@rag-forge/cli 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1260 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import { existsSync } from "fs";
8
+ import { cp, readFile, writeFile, readdir } from "fs/promises";
9
+ import { dirname, join, resolve } from "path";
10
+ import { fileURLToPath } from "url";
11
+
12
+ // src/lib/logger.ts
13
+ import chalk from "chalk";
14
+ var logger = {
15
+ info(message) {
16
+ console.log(chalk.blue("\u2139"), message);
17
+ },
18
+ success(message) {
19
+ console.log(chalk.green("\u2713"), message);
20
+ },
21
+ warn(message) {
22
+ console.log(chalk.yellow("\u26A0"), message);
23
+ },
24
+ error(message) {
25
+ console.error(chalk.red("\u2717"), message);
26
+ },
27
+ debug(message) {
28
+ if (process.env["DEBUG"]) {
29
+ console.log(chalk.gray("\u22A1"), message);
30
+ }
31
+ }
32
+ };
33
+
34
+ // src/commands/init.ts
35
+ var AVAILABLE_TEMPLATES = ["basic", "hybrid", "agentic", "enterprise", "n8n"];
36
+ function getTemplatesDir() {
37
+ const currentDir = dirname(fileURLToPath(import.meta.url));
38
+ return resolve(currentDir, "..", "..", "..", "templates");
39
+ }
40
+ async function listFiles(dir, prefix = "") {
41
+ const entries = await readdir(dir, { withFileTypes: true });
42
+ const files = [];
43
+ for (const entry of entries) {
44
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
45
+ if (entry.isDirectory()) {
46
+ files.push(...await listFiles(join(dir, entry.name), rel));
47
+ } else {
48
+ files.push(rel);
49
+ }
50
+ }
51
+ return files;
52
+ }
53
+ function registerInitCommand(program2) {
54
+ program2.command("init").argument("[template]", "Project template to use", "basic").option("-d, --directory <dir>", "Target directory").option("--no-install", "Skip dependency installation").description("Scaffold a new RAG project from a template").action(async (template, options) => {
55
+ const templateName = template;
56
+ if (!AVAILABLE_TEMPLATES.includes(templateName)) {
57
+ logger.error(
58
+ `Unknown template "${template}". Available: ${AVAILABLE_TEMPLATES.join(", ")}`
59
+ );
60
+ process.exit(1);
61
+ }
62
+ const targetDir = resolve(options.directory ?? templateName);
63
+ if (existsSync(targetDir) && options.directory !== ".") {
64
+ const entries = await readdir(targetDir);
65
+ if (entries.length > 0) {
66
+ logger.error(`Directory "${targetDir}" already exists and is not empty.`);
67
+ process.exit(1);
68
+ }
69
+ }
70
+ const templatesDir = getTemplatesDir();
71
+ const sourceDir = join(templatesDir, templateName, "project");
72
+ if (!existsSync(sourceDir)) {
73
+ logger.error(`Template "${templateName}" not found at ${sourceDir}`);
74
+ process.exit(1);
75
+ }
76
+ logger.info(`Scaffolding ${templateName} RAG project in ${targetDir}...`);
77
+ await cp(sourceDir, targetDir, { recursive: true });
78
+ const projectName = targetDir.split(/[/\\]/).pop() ?? templateName;
79
+ const pyprojectPath = join(targetDir, "pyproject.toml");
80
+ if (existsSync(pyprojectPath)) {
81
+ let content = await readFile(pyprojectPath, "utf-8");
82
+ content = content.replace(/my-rag-pipeline/g, projectName);
83
+ await writeFile(pyprojectPath, content, "utf-8");
84
+ }
85
+ const files = await listFiles(targetDir);
86
+ for (const file of files) {
87
+ logger.success(` Created ${file}`);
88
+ }
89
+ logger.info("");
90
+ logger.info("Next steps:");
91
+ logger.info(` cd ${targetDir}`);
92
+ logger.info(" rag-forge index --source ./docs");
93
+ logger.info(" rag-forge audit --golden-set eval/golden_set.json");
94
+ });
95
+ }
96
+
97
+ // src/commands/index.ts
98
+ import ora from "ora";
99
+
100
+ // src/lib/python-bridge.ts
101
+ import {
102
+ runPythonModule,
103
+ checkPythonAvailable
104
+ } from "@rag-forge/shared";
105
+
106
+ // src/commands/index.ts
107
+ function registerIndexCommand(program2) {
108
+ program2.command("index").requiredOption("-s, --source <dir>", "Source directory of documents to index").option("-c, --collection <name>", "Collection name", "rag-forge").option("-e, --embedding <provider>", "Embedding provider: openai | local | mock", "mock").option("--strategy <name>", "Chunking strategy: fixed | recursive | semantic | structural | llm-driven", "recursive").option(
109
+ "--chunking-generator <provider>",
110
+ "Generator for LLM-driven chunking: claude | openai | mock"
111
+ ).option("--enrich", "Enable contextual enrichment (document summary prepending)").option(
112
+ "--sparse-index-path <path>",
113
+ "Path to persist BM25 sparse index"
114
+ ).option(
115
+ "--enrichment-generator <provider>",
116
+ "Generator for enrichment summaries: claude | openai | mock"
117
+ ).description("Index documents into the vector store").action(
118
+ async (options) => {
119
+ const spinner = ora("Indexing documents...").start();
120
+ try {
121
+ const configJson = JSON.stringify({
122
+ embedding_provider: options.embedding,
123
+ collection_name: options.collection,
124
+ chunk_size: 512,
125
+ overlap_ratio: 0.1
126
+ });
127
+ const args = [
128
+ "index",
129
+ "--source",
130
+ options.source,
131
+ "--collection",
132
+ options.collection,
133
+ "--embedding",
134
+ options.embedding,
135
+ "--config-json",
136
+ configJson
137
+ ];
138
+ args.push("--strategy", options.strategy);
139
+ if (options.strategy === "llm-driven" && !options.chunkingGenerator) {
140
+ spinner.fail("--chunking-generator is required for llm-driven strategy");
141
+ process.exit(1);
142
+ }
143
+ if (options.chunkingGenerator) {
144
+ args.push("--chunking-generator", options.chunkingGenerator);
145
+ }
146
+ if (options.enrichmentGenerator && !options.enrich) {
147
+ spinner.fail("--enrichment-generator requires --enrich");
148
+ process.exit(1);
149
+ }
150
+ if (options.enrich) {
151
+ args.push("--enrich");
152
+ }
153
+ if (options.sparseIndexPath) {
154
+ args.push("--sparse-index-path", options.sparseIndexPath);
155
+ }
156
+ if (options.enrichmentGenerator) {
157
+ args.push("--enrichment-generator", options.enrichmentGenerator);
158
+ }
159
+ const result = await runPythonModule({
160
+ module: "rag_forge_core.cli",
161
+ args
162
+ });
163
+ if (result.exitCode !== 0) {
164
+ spinner.fail("Indexing failed");
165
+ logger.error(result.stderr || "Unknown error during indexing");
166
+ process.exit(1);
167
+ }
168
+ const output = JSON.parse(result.stdout);
169
+ if (output.success) {
170
+ spinner.succeed("Indexing complete");
171
+ logger.info(`Documents processed: ${String(output.documents_processed)}`);
172
+ logger.info(`Chunks created: ${String(output.chunks_created)}`);
173
+ logger.info(`Chunks indexed: ${String(output.chunks_indexed)}`);
174
+ if (output.enrichment_summaries > 0) {
175
+ logger.info(
176
+ `Documents enriched: ${String(output.enrichment_summaries)}`
177
+ );
178
+ }
179
+ } else {
180
+ spinner.warn("Indexing completed with errors");
181
+ for (const error of output.errors) {
182
+ logger.error(error);
183
+ }
184
+ process.exit(1);
185
+ }
186
+ } catch (error) {
187
+ spinner.fail("Indexing failed");
188
+ const message = error instanceof Error ? error.message : "Unknown error";
189
+ logger.error(message);
190
+ process.exit(1);
191
+ }
192
+ }
193
+ );
194
+ }
195
+
196
+ // src/commands/audit.ts
197
+ import ora2 from "ora";
198
+ function registerAuditCommand(program2) {
199
+ program2.command("audit").option("-i, --input <file>", "Path to telemetry JSONL file").option("-g, --golden-set <file>", "Path to golden set JSON file").option("-j, --judge <model>", "Judge model: mock | claude | openai", "mock").option("-o, --output <dir>", "Output directory for reports", "./reports").option("--evaluator <engine>", "Evaluator engine: llm-judge | ragas | deepeval", "llm-judge").option("--pdf", "Generate PDF report (requires Playwright)").description("Run evaluation on pipeline telemetry and generate audit report").action(
200
+ async (options) => {
201
+ if (!options.input && !options.goldenSet) {
202
+ logger.error("Either --input or --golden-set is required");
203
+ process.exit(1);
204
+ }
205
+ const spinner = ora2("Running RAG pipeline audit...").start();
206
+ try {
207
+ const args = ["audit", "--judge", options.judge, "--output", options.output, "--evaluator", options.evaluator];
208
+ if (options.input) {
209
+ args.push("--input", options.input);
210
+ }
211
+ if (options.goldenSet) {
212
+ args.push("--golden-set", options.goldenSet);
213
+ }
214
+ if (options.pdf) {
215
+ args.push("--pdf");
216
+ }
217
+ const result = await runPythonModule({
218
+ module: "rag_forge_evaluator.cli",
219
+ args
220
+ });
221
+ if (result.exitCode !== 0) {
222
+ spinner.fail("Audit failed");
223
+ logger.error(result.stderr || "Unknown error during audit");
224
+ process.exit(1);
225
+ }
226
+ const output = JSON.parse(result.stdout);
227
+ if (output.passed) {
228
+ spinner.succeed(
229
+ `Audit passed \u2014 RMM-${String(output.rmm_level)}: ${output.rmm_name}`
230
+ );
231
+ } else {
232
+ spinner.warn(
233
+ `Audit completed \u2014 RMM-${String(output.rmm_level)}: ${output.rmm_name}`
234
+ );
235
+ }
236
+ logger.info(`Overall score: ${output.overall_score.toFixed(2)}`);
237
+ logger.info(`Samples evaluated: ${String(output.samples_evaluated)}`);
238
+ for (const metric of output.metrics) {
239
+ const status = metric.passed ? "PASS" : "FAIL";
240
+ logger.info(
241
+ ` ${metric.name}: ${metric.score.toFixed(2)} (threshold: ${metric.threshold.toFixed(2)}) [${status}]`
242
+ );
243
+ }
244
+ logger.info(`Evaluator: ${output.evaluator_engine}`);
245
+ logger.success(`Report saved to: ${output.report_path}`);
246
+ if (output.pdf_report_path) {
247
+ logger.success(`PDF report: ${output.pdf_report_path}`);
248
+ }
249
+ } catch (error) {
250
+ spinner.fail("Audit failed");
251
+ const message = error instanceof Error ? error.message : "Unknown error";
252
+ logger.error(message);
253
+ process.exit(1);
254
+ }
255
+ }
256
+ );
257
+ }
258
+
259
+ // src/commands/query.ts
260
+ import ora3 from "ora";
261
+ function registerQueryCommand(program2) {
262
+ program2.command("query").argument("<question>", "The question to ask the RAG pipeline").option("-k, --top-k <number>", "Number of chunks to retrieve", "5").option("-e, --embedding <provider>", "Embedding provider: openai | local | mock", "mock").option("-g, --generator <provider>", "Generation provider: claude | openai | mock", "mock").option("-c, --collection <name>", "Collection name", "rag-forge").option(
263
+ "--strategy <type>",
264
+ "Retrieval strategy: dense | sparse | hybrid",
265
+ "dense"
266
+ ).option(
267
+ "--alpha <number>",
268
+ "RRF alpha weighting for hybrid retrieval (0.0-1.0)",
269
+ "0.6"
270
+ ).option(
271
+ "--reranker <type>",
272
+ "Reranker: none | cohere | bge-local",
273
+ "none"
274
+ ).option(
275
+ "--sparse-index-path <path>",
276
+ "Path to BM25 sparse index"
277
+ ).option("--input-guard", "Enable input security guard").option("--output-guard", "Enable output security guard").option(
278
+ "--faithfulness-threshold <number>",
279
+ "Faithfulness score threshold (0.0-1.0)",
280
+ "0.85"
281
+ ).option("--rate-limit <number>", "Max queries per minute", "60").option("--cache", "Enable semantic query caching").option("--cache-ttl <seconds>", "Cache TTL in seconds", "3600").option("--cache-similarity <threshold>", "Cosine similarity threshold", "0.95").option("--agent-mode", "Enable multi-query decomposition").description("Execute a RAG query against the indexed pipeline").action(
282
+ async (question, options) => {
283
+ const spinner = ora3("Querying pipeline...").start();
284
+ try {
285
+ const args = [
286
+ "query",
287
+ "--question",
288
+ question,
289
+ "--embedding",
290
+ options.embedding,
291
+ "--generator",
292
+ options.generator,
293
+ "--collection",
294
+ options.collection,
295
+ "--top-k",
296
+ options.topK,
297
+ "--strategy",
298
+ options.strategy,
299
+ "--alpha",
300
+ options.alpha,
301
+ "--reranker",
302
+ options.reranker
303
+ ];
304
+ if (options.sparseIndexPath) {
305
+ args.push("--sparse-index-path", options.sparseIndexPath);
306
+ }
307
+ if (options.inputGuard) {
308
+ args.push("--input-guard");
309
+ }
310
+ if (options.outputGuard) {
311
+ args.push("--output-guard");
312
+ }
313
+ args.push("--faithfulness-threshold", options.faithfulnessThreshold);
314
+ args.push("--rate-limit", options.rateLimit);
315
+ if (options.cache) {
316
+ args.push("--cache");
317
+ }
318
+ args.push("--cache-ttl", options.cacheTtl);
319
+ args.push("--cache-similarity", options.cacheSimilarity);
320
+ if (options.agentMode) {
321
+ args.push("--agent-mode");
322
+ }
323
+ const result = await runPythonModule({
324
+ module: "rag_forge_core.cli",
325
+ args
326
+ });
327
+ if (result.exitCode !== 0) {
328
+ spinner.fail("Query failed");
329
+ logger.error(result.stderr || "Unknown error");
330
+ process.exit(1);
331
+ }
332
+ const output = JSON.parse(result.stdout);
333
+ if (output.blocked) {
334
+ spinner.warn(`Query blocked: ${output.blocked_reason ?? "Unknown reason"}`);
335
+ return;
336
+ }
337
+ spinner.succeed(`Answer (${output.model_used}):`);
338
+ console.log("");
339
+ console.log(output.answer);
340
+ console.log("");
341
+ if (output.sources.length > 0) {
342
+ logger.info(`Sources (${String(output.chunks_retrieved)} chunks):`);
343
+ for (const source of output.sources) {
344
+ logger.info(
345
+ ` [${source.score.toFixed(3)}] ${source.text.slice(0, 120)}...`
346
+ );
347
+ }
348
+ }
349
+ } catch (error) {
350
+ spinner.fail("Query failed");
351
+ const message = error instanceof Error ? error.message : "Unknown error";
352
+ logger.error(message);
353
+ process.exit(1);
354
+ }
355
+ }
356
+ );
357
+ }
358
+
359
+ // src/commands/serve.ts
360
+ import { spawn } from "child_process";
361
+ import { existsSync as existsSync2 } from "fs";
362
+ import { resolve as resolve2 } from "path";
363
+ import { fileURLToPath as fileURLToPath2 } from "url";
364
+ function getMcpMainPath() {
365
+ const currentDir = fileURLToPath2(new URL(".", import.meta.url));
366
+ return resolve2(currentDir, "..", "..", "..", "mcp", "dist", "main.js");
367
+ }
368
+ function registerServeCommand(program2) {
369
+ program2.command("serve").option("--mcp", "Launch MCP server").option("--transport <type>", "Transport: stdio | http", "stdio").option("-p, --port <number>", "Port for HTTP transport", "3100").description("Start the RAG-Forge server").action(async (options) => {
370
+ if (!options.mcp) {
371
+ logger.error("Please specify a server mode. Currently supported: --mcp");
372
+ process.exit(1);
373
+ }
374
+ const mcpMain = getMcpMainPath();
375
+ if (!existsSync2(mcpMain)) {
376
+ logger.error(`MCP server not found at ${mcpMain}. Run 'pnpm run build' first.`);
377
+ process.exit(1);
378
+ }
379
+ const args = [mcpMain];
380
+ if (options.transport === "http") {
381
+ args.push("--transport", "http", "--port", options.port);
382
+ logger.info(`Starting MCP server on http://localhost:${options.port}/sse`);
383
+ } else {
384
+ logger.info("Starting MCP server on stdio...");
385
+ }
386
+ const child = spawn(process.execPath, args, {
387
+ stdio: "inherit"
388
+ });
389
+ child.on("error", (error) => {
390
+ logger.error(`MCP server failed: ${error.message}`);
391
+ process.exit(1);
392
+ });
393
+ child.on("exit", (code) => {
394
+ process.exit(code ?? 0);
395
+ });
396
+ });
397
+ }
398
+
399
+ // src/commands/drift.ts
400
+ import ora4 from "ora";
401
+ function registerDriftCommand(program2) {
402
+ const drift = program2.command("drift").description("Query drift detection and baseline management");
403
+ drift.command("report").requiredOption("--current <file>", "Path to current embeddings JSON").requiredOption("--baseline <file>", "Path to baseline embeddings JSON").option("--threshold <number>", "Drift threshold (cosine distance)", "0.15").description("Analyze query distribution drift from baseline").action(
404
+ async (options) => {
405
+ const spinner = ora4("Analyzing query drift...").start();
406
+ try {
407
+ const result = await runPythonModule({
408
+ module: "rag_forge_observability.cli",
409
+ args: [
410
+ "drift-report",
411
+ "--current",
412
+ options.current,
413
+ "--baseline",
414
+ options.baseline,
415
+ "--threshold",
416
+ options.threshold
417
+ ]
418
+ });
419
+ if (result.exitCode !== 0) {
420
+ spinner.fail("Drift analysis failed");
421
+ logger.error(result.stderr || "Unknown error");
422
+ process.exit(1);
423
+ }
424
+ const output = JSON.parse(result.stdout);
425
+ if (!output.success) {
426
+ spinner.fail("Drift analysis failed");
427
+ logger.error(output.error ?? "Unknown error");
428
+ process.exit(1);
429
+ }
430
+ if (output.is_drifting) {
431
+ spinner.warn(
432
+ `DRIFT DETECTED \u2014 distance: ${output.distance?.toFixed(4)} (threshold: ${output.threshold?.toFixed(4)})`
433
+ );
434
+ } else {
435
+ spinner.succeed(
436
+ `No drift \u2014 distance: ${output.distance?.toFixed(4)} (threshold: ${output.threshold?.toFixed(4)})`
437
+ );
438
+ }
439
+ if (output.details) {
440
+ logger.info(output.details);
441
+ }
442
+ } catch (error) {
443
+ spinner.fail("Drift analysis failed");
444
+ const message = error instanceof Error ? error.message : "Unknown error";
445
+ logger.error(message);
446
+ process.exit(1);
447
+ }
448
+ }
449
+ );
450
+ drift.command("save-baseline").requiredOption("--embeddings <file>", "Path to embeddings JSON").requiredOption("--output <file>", "Path to save baseline").description("Save current embeddings as drift baseline").action(async (options) => {
451
+ const spinner = ora4("Saving drift baseline...").start();
452
+ try {
453
+ const result = await runPythonModule({
454
+ module: "rag_forge_observability.cli",
455
+ args: [
456
+ "drift-save-baseline",
457
+ "--embeddings",
458
+ options.embeddings,
459
+ "--output",
460
+ options.output
461
+ ]
462
+ });
463
+ if (result.exitCode !== 0) {
464
+ spinner.fail("Failed to save baseline");
465
+ logger.error(result.stderr || "Unknown error");
466
+ process.exit(1);
467
+ }
468
+ const output = JSON.parse(result.stdout);
469
+ if (output.success) {
470
+ spinner.succeed(
471
+ `Baseline saved: ${String(output.vectors_saved)} vectors \u2192 ${output.baseline_path}`
472
+ );
473
+ } else {
474
+ spinner.fail("Failed to save baseline");
475
+ logger.error(output.error ?? "Unknown error");
476
+ process.exit(1);
477
+ }
478
+ } catch (error) {
479
+ spinner.fail("Failed to save baseline");
480
+ const message = error instanceof Error ? error.message : "Unknown error";
481
+ logger.error(message);
482
+ process.exit(1);
483
+ }
484
+ });
485
+ }
486
+
487
+ // src/commands/cost.ts
488
+ import ora5 from "ora";
489
+ function registerCostCommand(program2) {
490
+ program2.command("cost").requiredOption("--telemetry <file>", "Path to telemetry JSON file").option(
491
+ "--queries-per-day <number>",
492
+ "Projected daily query volume (overrides telemetry file)"
493
+ ).description("Estimate monthly pipeline costs from telemetry").action(
494
+ async (options) => {
495
+ const spinner = ora5("Estimating costs...").start();
496
+ try {
497
+ const args = ["cost", "--telemetry", options.telemetry];
498
+ if (options.queriesPerDay) {
499
+ args.push("--queries-per-day", options.queriesPerDay);
500
+ }
501
+ const result = await runPythonModule({
502
+ module: "rag_forge_evaluator.cli",
503
+ args
504
+ });
505
+ if (result.exitCode !== 0) {
506
+ let errorMessage = result.stderr || "Unknown error";
507
+ try {
508
+ const parsed = JSON.parse(result.stdout);
509
+ if (parsed.error) errorMessage = parsed.error;
510
+ } catch {
511
+ }
512
+ spinner.fail("Cost estimation failed");
513
+ logger.error(errorMessage);
514
+ process.exit(1);
515
+ }
516
+ const output = JSON.parse(result.stdout);
517
+ if (!output.success) {
518
+ spinner.fail("Cost estimation failed");
519
+ logger.error(output.error ?? "Unknown error");
520
+ process.exit(1);
521
+ }
522
+ spinner.succeed("Cost estimation complete");
523
+ logger.info(
524
+ `Daily cost: $${output.daily_cost.toFixed(4)} (${String(output.queries_per_day)} queries/day)`
525
+ );
526
+ logger.info(`Monthly cost: $${output.monthly_cost.toFixed(2)}`);
527
+ if (output.breakdown.length > 0) {
528
+ logger.info("Breakdown by model:");
529
+ for (const item of output.breakdown) {
530
+ logger.info(
531
+ ` ${item.model}: $${item.cost_per_query.toFixed(6)}/query`
532
+ );
533
+ }
534
+ }
535
+ } catch (error) {
536
+ spinner.fail("Cost estimation failed");
537
+ const message = error instanceof Error ? error.message : "Unknown error";
538
+ logger.error(message);
539
+ process.exit(1);
540
+ }
541
+ }
542
+ );
543
+ }
544
+
545
+ // src/commands/golden.ts
546
+ import ora6 from "ora";
547
+ function registerGoldenCommand(program2) {
548
+ const golden = program2.command("golden").description("Golden set management for evaluation");
549
+ golden.command("add").requiredOption("-g, --golden-set <file>", "Path to golden set JSON", "eval/golden_set.json").option("--from-traffic <file>", "Sample from telemetry JSONL file").option("--sample-size <number>", "Number of entries to sample", "10").option("--query <question>", "Question to add manually").option("--keywords <list>", "Comma-separated expected keywords").option("--difficulty <level>", "Difficulty: easy | medium | hard", "medium").option("--topic <name>", "Topic category", "general").description("Add entries to the golden set (manual or from traffic)").action(
550
+ async (options) => {
551
+ const spinner = ora6("Adding to golden set...").start();
552
+ try {
553
+ const args = [
554
+ "golden-add",
555
+ "--golden-set",
556
+ options.goldenSet
557
+ ];
558
+ if (options.fromTraffic) {
559
+ args.push("--from-traffic", options.fromTraffic);
560
+ args.push("--sample-size", options.sampleSize);
561
+ } else if (options.query && options.keywords) {
562
+ args.push("--query", options.query);
563
+ args.push("--keywords", options.keywords);
564
+ args.push("--difficulty", options.difficulty);
565
+ args.push("--topic", options.topic);
566
+ } else {
567
+ spinner.fail("Provide --from-traffic <file> or --query <q> --keywords <k>");
568
+ process.exit(1);
569
+ }
570
+ const result = await runPythonModule({
571
+ module: "rag_forge_evaluator.cli",
572
+ args
573
+ });
574
+ if (result.exitCode !== 0) {
575
+ let errorMessage = result.stderr || "Unknown error";
576
+ try {
577
+ const parsed = JSON.parse(result.stdout);
578
+ if (parsed.error) errorMessage = parsed.error;
579
+ } catch {
580
+ }
581
+ spinner.fail("Failed to add to golden set");
582
+ logger.error(errorMessage);
583
+ process.exit(1);
584
+ }
585
+ const output = JSON.parse(result.stdout);
586
+ if (output.success) {
587
+ spinner.succeed(
588
+ `Added ${String(output.added)} entries (total: ${String(output.total)}) \u2192 ${output.golden_set_path}`
589
+ );
590
+ } else {
591
+ spinner.fail("Failed to add to golden set");
592
+ logger.error(output.error ?? "Unknown error");
593
+ process.exit(1);
594
+ }
595
+ } catch (error) {
596
+ spinner.fail("Failed to add to golden set");
597
+ const message = error instanceof Error ? error.message : "Unknown error";
598
+ logger.error(message);
599
+ process.exit(1);
600
+ }
601
+ }
602
+ );
603
+ golden.command("validate").requiredOption("-g, --golden-set <file>", "Path to golden set JSON", "eval/golden_set.json").description("Validate golden set coverage, balance, and schema").action(
604
+ async (options) => {
605
+ const spinner = ora6("Validating golden set...").start();
606
+ try {
607
+ const result = await runPythonModule({
608
+ module: "rag_forge_evaluator.cli",
609
+ args: ["golden-validate", "--golden-set", options.goldenSet]
610
+ });
611
+ if (result.exitCode !== 0) {
612
+ let errorMessage = result.stderr || "Unknown error";
613
+ try {
614
+ const parsed = JSON.parse(result.stdout);
615
+ if (parsed.error) errorMessage = parsed.error;
616
+ } catch {
617
+ }
618
+ spinner.fail("Validation failed");
619
+ logger.error(errorMessage);
620
+ process.exit(1);
621
+ }
622
+ const output = JSON.parse(result.stdout);
623
+ if (output.valid) {
624
+ spinner.succeed(
625
+ `Golden set valid \u2014 ${String(output.total_entries)} entries, no issues`
626
+ );
627
+ } else {
628
+ spinner.warn(
629
+ `Golden set has ${String(output.errors.length)} issue(s):`
630
+ );
631
+ for (const err of output.errors) {
632
+ logger.warn(` - ${err}`);
633
+ }
634
+ }
635
+ } catch (error) {
636
+ spinner.fail("Validation failed");
637
+ const message = error instanceof Error ? error.message : "Unknown error";
638
+ logger.error(message);
639
+ process.exit(1);
640
+ }
641
+ }
642
+ );
643
+ }
644
+
645
+ // src/commands/assess.ts
646
+ import ora7 from "ora";
647
+ function registerAssessCommand(program2) {
648
+ program2.command("assess").option("--config <json>", "Pipeline config as JSON string").option("--audit-report <file>", "Path to latest audit JSON report").description("Run RAG Maturity Model assessment").action(
649
+ async (options) => {
650
+ const spinner = ora7("Running RMM assessment...").start();
651
+ try {
652
+ const args = ["assess"];
653
+ if (options.config) {
654
+ args.push("--config-json", options.config);
655
+ }
656
+ if (options.auditReport) {
657
+ args.push("--audit-report", options.auditReport);
658
+ }
659
+ const result = await runPythonModule({
660
+ module: "rag_forge_evaluator.cli",
661
+ args
662
+ });
663
+ if (result.exitCode !== 0) {
664
+ let errorMessage = result.stderr || "Unknown error";
665
+ try {
666
+ const parsed = JSON.parse(result.stdout);
667
+ if (parsed.error) errorMessage = parsed.error;
668
+ } catch {
669
+ }
670
+ spinner.fail("Assessment failed");
671
+ logger.error(errorMessage);
672
+ process.exit(1);
673
+ }
674
+ const output = JSON.parse(result.stdout);
675
+ if (!output.success) {
676
+ spinner.fail("Assessment failed");
677
+ logger.error(output.error ?? "Unknown error");
678
+ process.exit(1);
679
+ }
680
+ spinner.succeed(output.badge);
681
+ for (const criteria of output.criteria) {
682
+ const icon = criteria.passed ? "PASS" : "----";
683
+ logger.info(` [${icon}] RMM-${String(criteria.level)}: ${criteria.name}`);
684
+ for (const check of criteria.checks) {
685
+ const checkIcon = check.passed ? "+" : "-";
686
+ const source = check.source !== "config" ? ` (${check.source})` : "";
687
+ logger.info(` [${checkIcon}] ${check.description}${source}`);
688
+ }
689
+ }
690
+ } catch (error) {
691
+ spinner.fail("Assessment failed");
692
+ const message = error instanceof Error ? error.message : "Unknown error";
693
+ logger.error(message);
694
+ process.exit(1);
695
+ }
696
+ }
697
+ );
698
+ }
699
+
700
+ // src/commands/guardrails.ts
701
+ import ora8 from "ora";
702
+ function registerGuardrailsCommand(program2) {
703
+ const guardrails = program2.command("guardrails").description("Security testing and PII scanning");
704
+ guardrails.command("test").option("--corpus <file>", "Path to custom adversarial corpus JSON").description("Run adversarial prompt test suite against security guards").action(
705
+ async (options) => {
706
+ const spinner = ora8("Running adversarial tests...").start();
707
+ try {
708
+ const args = ["guardrails-test"];
709
+ if (options.corpus) {
710
+ args.push("--corpus", options.corpus);
711
+ }
712
+ const result = await runPythonModule({
713
+ module: "rag_forge_core.cli",
714
+ args
715
+ });
716
+ if (result.exitCode !== 0) {
717
+ let errorMessage = result.stderr || "Unknown error";
718
+ try {
719
+ const parsed = JSON.parse(result.stdout);
720
+ if (parsed.error) errorMessage = parsed.error;
721
+ } catch {
722
+ }
723
+ spinner.fail("Adversarial tests failed");
724
+ logger.error(errorMessage);
725
+ process.exit(1);
726
+ }
727
+ const output = JSON.parse(result.stdout);
728
+ if (!output.success) {
729
+ spinner.fail("Adversarial tests failed");
730
+ logger.error(output.error ?? "Unknown error");
731
+ process.exit(1);
732
+ }
733
+ const passPercent = (output.pass_rate * 100).toFixed(1);
734
+ if (output.failures.length === 0) {
735
+ spinner.succeed(
736
+ `All attacks blocked \u2014 ${String(output.blocked)}/${String(output.total_tested)} (${passPercent}% block rate)`
737
+ );
738
+ } else {
739
+ spinner.warn(
740
+ `${String(output.failures.length)} attacks got through \u2014 ${String(output.blocked)}/${String(output.total_tested)} blocked (${passPercent}% block rate)`
741
+ );
742
+ }
743
+ logger.info("By category:");
744
+ for (const [cat, stats] of Object.entries(output.by_category)) {
745
+ const rate = (stats.pass_rate * 100).toFixed(0);
746
+ logger.info(` ${cat}: ${String(stats.blocked)}/${String(stats.tested)} blocked (${rate}%)`);
747
+ }
748
+ if (output.failures.length > 0) {
749
+ logger.warn("Failures (attacks that got through):");
750
+ for (const failure of output.failures) {
751
+ logger.warn(` [${failure.severity}] ${failure.category}: ${failure.text.slice(0, 80)}...`);
752
+ }
753
+ }
754
+ } catch (error) {
755
+ spinner.fail("Adversarial tests failed");
756
+ const message = error instanceof Error ? error.message : "Unknown error";
757
+ logger.error(message);
758
+ process.exit(1);
759
+ }
760
+ }
761
+ );
762
+ guardrails.command("scan-pii").option("-c, --collection <name>", "Collection name", "rag-forge").description("Scan vector store for PII leakage").action(
763
+ async (options) => {
764
+ const spinner = ora8("Scanning for PII...").start();
765
+ try {
766
+ const result = await runPythonModule({
767
+ module: "rag_forge_core.cli",
768
+ args: ["guardrails-scan-pii", "--collection", options.collection]
769
+ });
770
+ if (result.exitCode !== 0) {
771
+ let errorMessage = result.stderr || "Unknown error";
772
+ try {
773
+ const parsed = JSON.parse(result.stdout);
774
+ if (parsed.error) errorMessage = parsed.error;
775
+ } catch {
776
+ }
777
+ spinner.fail("PII scan failed");
778
+ logger.error(errorMessage);
779
+ process.exit(1);
780
+ }
781
+ const output = JSON.parse(result.stdout);
782
+ if (!output.success) {
783
+ spinner.fail("PII scan failed");
784
+ logger.error(output.error ?? "Unknown error");
785
+ process.exit(1);
786
+ }
787
+ if (output.chunks_with_pii === 0) {
788
+ spinner.succeed(
789
+ `No PII found \u2014 ${String(output.chunks_scanned)} chunks scanned`
790
+ );
791
+ } else {
792
+ spinner.warn(
793
+ `PII found in ${String(output.chunks_with_pii)}/${String(output.chunks_scanned)} chunks`
794
+ );
795
+ logger.warn("PII types detected:");
796
+ for (const [piiType, count] of Object.entries(output.pii_types)) {
797
+ logger.warn(` ${piiType}: ${String(count)} occurrences`);
798
+ }
799
+ }
800
+ } catch (error) {
801
+ spinner.fail("PII scan failed");
802
+ const message = error instanceof Error ? error.message : "Unknown error";
803
+ logger.error(message);
804
+ process.exit(1);
805
+ }
806
+ }
807
+ );
808
+ }
809
+
810
+ // src/commands/report.ts
811
+ import ora9 from "ora";
812
+ function registerReportCommand(program2) {
813
+ program2.command("report").option("-o, --output <dir>", "Output directory for reports", "./reports").option("--collection <name>", "Collection name", "rag-forge").description("Generate a pipeline health report dashboard").action(
814
+ async (options) => {
815
+ const spinner = ora9("Generating health report...").start();
816
+ try {
817
+ const result = await runPythonModule({
818
+ module: "rag_forge_evaluator.cli",
819
+ args: [
820
+ "report",
821
+ "--output",
822
+ options.output,
823
+ "--collection",
824
+ options.collection
825
+ ]
826
+ });
827
+ if (result.exitCode !== 0) {
828
+ let errorMessage = result.stderr || "Unknown error";
829
+ try {
830
+ const parsed = JSON.parse(result.stdout);
831
+ if (parsed.error) errorMessage = parsed.error;
832
+ } catch {
833
+ }
834
+ spinner.fail("Report generation failed");
835
+ logger.error(errorMessage);
836
+ process.exit(1);
837
+ }
838
+ const output = JSON.parse(result.stdout);
839
+ if (!output.success) {
840
+ spinner.fail("Report generation failed");
841
+ logger.error(output.error ?? "Unknown error");
842
+ process.exit(1);
843
+ }
844
+ spinner.succeed("Health report generated");
845
+ logger.info(
846
+ `Indexed chunks: ${String(output.chunk_count ?? 0)}`
847
+ );
848
+ logger.info(
849
+ `Audit data: ${output.has_audit ? "included" : "none available"}`
850
+ );
851
+ logger.info(
852
+ `Drift baseline: ${output.drift_baseline ? "configured" : "not configured"}`
853
+ );
854
+ logger.success(`Report saved to: ${output.report_path ?? "unknown"}`);
855
+ } catch (error) {
856
+ spinner.fail("Report generation failed");
857
+ const message = error instanceof Error ? error.message : "Unknown error";
858
+ logger.error(message);
859
+ process.exit(1);
860
+ }
861
+ }
862
+ );
863
+ }
864
+
865
+ // src/commands/cache.ts
866
+ import ora10 from "ora";
867
+ function registerCacheCommand(program2) {
868
+ const cache = program2.command("cache").description("Semantic cache management");
869
+ cache.command("stats").description("Show semantic cache hit/miss statistics").action(async () => {
870
+ const spinner = ora10("Fetching cache statistics...").start();
871
+ try {
872
+ const result = await runPythonModule({
873
+ module: "rag_forge_core.cli",
874
+ args: ["cache-stats"]
875
+ });
876
+ if (result.exitCode !== 0) {
877
+ let errorMessage = result.stderr || "Unknown error";
878
+ try {
879
+ const parsed = JSON.parse(
880
+ result.stdout
881
+ );
882
+ if (parsed.error) errorMessage = parsed.error;
883
+ } catch {
884
+ }
885
+ spinner.fail("Failed to fetch cache stats");
886
+ logger.error(errorMessage);
887
+ process.exit(1);
888
+ }
889
+ const output = JSON.parse(result.stdout);
890
+ if (!output.success) {
891
+ spinner.fail("Failed to fetch cache stats");
892
+ logger.error(output.error ?? "Unknown error");
893
+ process.exit(1);
894
+ }
895
+ spinner.succeed("Cache statistics");
896
+ const hitRate = output.hit_rate ?? 0;
897
+ const hitRatePercent = (hitRate * 100).toFixed(1);
898
+ logger.info(`Hit rate: ${hitRatePercent}%`);
899
+ logger.info(`Hits: ${String(output.hits ?? 0)}`);
900
+ logger.info(`Misses: ${String(output.misses ?? 0)}`);
901
+ logger.info(`Total: ${String(output.total ?? 0)}`);
902
+ logger.info(`Source: ${output.source ?? "unknown"}`);
903
+ if (output.message) {
904
+ logger.info(output.message);
905
+ }
906
+ } catch (error) {
907
+ spinner.fail("Failed to fetch cache stats");
908
+ const message = error instanceof Error ? error.message : "Unknown error";
909
+ logger.error(message);
910
+ process.exit(1);
911
+ }
912
+ });
913
+ }
914
+
915
+ // src/commands/inspect.ts
916
+ import ora11 from "ora";
917
+ function registerInspectCommand(program2) {
918
+ program2.command("inspect").requiredOption("--chunk-id <id>", "The chunk ID to inspect").option("--collection <name>", "Collection name", "rag-forge").description("Inspect a specific chunk by ID").action(
919
+ async (options) => {
920
+ const spinner = ora11(
921
+ `Inspecting chunk ${options.chunkId}...`
922
+ ).start();
923
+ try {
924
+ const result = await runPythonModule({
925
+ module: "rag_forge_core.cli",
926
+ args: [
927
+ "inspect",
928
+ "--chunk-id",
929
+ options.chunkId,
930
+ "--collection",
931
+ options.collection
932
+ ]
933
+ });
934
+ if (result.exitCode !== 0) {
935
+ spinner.fail("Inspection failed");
936
+ logger.error(result.stderr || "Unknown error");
937
+ process.exit(1);
938
+ }
939
+ const output = JSON.parse(result.stdout);
940
+ if (!output.found) {
941
+ spinner.warn(
942
+ `Chunk not found: ${options.chunkId} in collection ${output.collection}`
943
+ );
944
+ if (output.error) {
945
+ logger.error(output.error);
946
+ }
947
+ return;
948
+ }
949
+ spinner.succeed(`Chunk found: ${output.chunk_id}`);
950
+ logger.info(`Collection: ${output.collection}`);
951
+ if (output.text !== void 0) {
952
+ logger.info("Text:");
953
+ logger.info(` ${output.text}`);
954
+ }
955
+ if (output.metadata) {
956
+ logger.info("Metadata:");
957
+ for (const [key, value] of Object.entries(output.metadata)) {
958
+ logger.info(` ${key}: ${String(value)}`);
959
+ }
960
+ }
961
+ } catch (error) {
962
+ spinner.fail("Inspection failed");
963
+ const message = error instanceof Error ? error.message : "Unknown error";
964
+ logger.error(message);
965
+ process.exit(1);
966
+ }
967
+ }
968
+ );
969
+ }
970
+
971
+ // src/commands/add.ts
972
+ import { existsSync as existsSync3, readFileSync } from "fs";
973
+ import { cp as cp2, mkdir } from "fs/promises";
974
+ import { dirname as dirname2, resolve as resolve3 } from "path";
975
+ import { fileURLToPath as fileURLToPath3 } from "url";
976
+ function getPackageRoot() {
977
+ const currentDir = dirname2(fileURLToPath3(import.meta.url));
978
+ if (currentDir.endsWith("commands") || currentDir.endsWith("commands\\") || currentDir.endsWith("commands/")) {
979
+ return resolve3(currentDir, "..", "..");
980
+ }
981
+ return resolve3(currentDir, "..");
982
+ }
983
+ function getManifestPath() {
984
+ return resolve3(getPackageRoot(), "src", "modules", "manifest.json");
985
+ }
986
+ function getTemplateSourceDir() {
987
+ return resolve3(getPackageRoot(), "..", "..", "templates", "enterprise", "project", "src");
988
+ }
989
+ function registerAddCommand(program2) {
990
+ program2.command("add").argument(
991
+ "<module>",
992
+ "Module to add: guardrails | caching | reranking | enrichment | observability | hybrid-retrieval"
993
+ ).description("Add a feature module as editable source code (shadcn/ui model)").action(async (moduleName) => {
994
+ const manifestPath = getManifestPath();
995
+ if (!existsSync3(manifestPath)) {
996
+ logger.error(`Module manifest not found at ${manifestPath}`);
997
+ process.exit(1);
998
+ }
999
+ const manifest = JSON.parse(
1000
+ readFileSync(manifestPath, "utf-8")
1001
+ );
1002
+ const mod = manifest.modules[moduleName];
1003
+ if (!mod) {
1004
+ const available = Object.keys(manifest.modules).join(", ");
1005
+ logger.error(`Unknown module: ${moduleName}. Available: ${available}`);
1006
+ process.exit(1);
1007
+ }
1008
+ logger.info(`Adding module: ${moduleName} \u2014 ${mod.description}`);
1009
+ const sourceDir = getTemplateSourceDir();
1010
+ let added = 0;
1011
+ let skipped = 0;
1012
+ let missingSources = 0;
1013
+ for (const file of mod.files) {
1014
+ const srcPath = resolve3(sourceDir, file.src);
1015
+ const destPath = resolve3(process.cwd(), file.dest);
1016
+ if (existsSync3(destPath)) {
1017
+ logger.warn(` Skip (exists): ${file.dest}`);
1018
+ skipped++;
1019
+ continue;
1020
+ }
1021
+ if (!existsSync3(srcPath)) {
1022
+ logger.warn(` Skip (source not found): ${file.src}`);
1023
+ missingSources++;
1024
+ continue;
1025
+ }
1026
+ await mkdir(dirname2(destPath), { recursive: true });
1027
+ await cp2(srcPath, destPath);
1028
+ logger.success(` Added: ${file.dest}`);
1029
+ added++;
1030
+ }
1031
+ logger.info(
1032
+ `
1033
+ Added ${String(added)} files, skipped ${String(skipped)}, missing ${String(missingSources)}`
1034
+ );
1035
+ if (added === 0 && missingSources > 0) {
1036
+ logger.error(
1037
+ "No files were added because all sources are missing. This indicates a packaging or manifest problem."
1038
+ );
1039
+ process.exit(1);
1040
+ }
1041
+ if (mod.dependencies.length > 0) {
1042
+ logger.info("\nInstall dependencies:");
1043
+ logger.info(` pip install ${mod.dependencies.join(" ")}`);
1044
+ }
1045
+ });
1046
+ }
1047
+
1048
+ // src/commands/parse.ts
1049
+ import ora12 from "ora";
1050
+ function registerParseCommand(program2) {
1051
+ program2.command("parse").option("-s, --source <directory>", "Source directory to parse", "./docs").description("Preview document extraction without indexing").action(async (options) => {
1052
+ const spinner = ora12("Parsing documents...").start();
1053
+ try {
1054
+ const args = ["parse", "--source", options.source];
1055
+ const result = await runPythonModule({
1056
+ module: "rag_forge_core.cli",
1057
+ args
1058
+ });
1059
+ if (result.exitCode !== 0) {
1060
+ let errorMessage = result.stderr || "Unknown error";
1061
+ try {
1062
+ const parsed = JSON.parse(result.stdout);
1063
+ if (parsed.error) errorMessage = parsed.error;
1064
+ } catch {
1065
+ }
1066
+ spinner.fail("Parse preview failed");
1067
+ logger.error(errorMessage);
1068
+ process.exit(1);
1069
+ }
1070
+ const output = JSON.parse(result.stdout);
1071
+ if (!output.success) {
1072
+ spinner.fail("Parse preview failed");
1073
+ logger.error(output.error ?? "Unknown error");
1074
+ process.exit(1);
1075
+ }
1076
+ spinner.succeed(
1077
+ `Found ${String(output.files_found)} files (${String(output.total_characters)} characters)`
1078
+ );
1079
+ if (output.files.length > 0) {
1080
+ console.log("");
1081
+ for (const file of output.files) {
1082
+ logger.info(` ${file.path} (${String(file.characters)} chars)`);
1083
+ }
1084
+ }
1085
+ if (output.parse_errors.length > 0) {
1086
+ console.log("");
1087
+ logger.warn(`${String(output.parse_errors.length)} parse errors:`);
1088
+ for (const err of output.parse_errors) {
1089
+ logger.warn(` ${err}`);
1090
+ }
1091
+ }
1092
+ } catch (error) {
1093
+ spinner.fail("Parse preview failed");
1094
+ const message = error instanceof Error ? error.message : "Unknown error";
1095
+ logger.error(message);
1096
+ process.exit(1);
1097
+ }
1098
+ });
1099
+ }
1100
+
1101
+ // src/commands/chunk.ts
1102
+ import ora13 from "ora";
1103
+ function registerChunkCommand(program2) {
1104
+ program2.command("chunk").option("-s, --source <directory>", "Source directory to chunk", "./docs").option(
1105
+ "--strategy <type>",
1106
+ "Chunking strategy: fixed | recursive | semantic | structural | llm-driven",
1107
+ "recursive"
1108
+ ).option("--chunk-size <tokens>", "Target chunk size in tokens").description("Preview chunking without indexing").action(
1109
+ async (options) => {
1110
+ const spinner = ora13("Chunking documents...").start();
1111
+ try {
1112
+ const args = [
1113
+ "chunk",
1114
+ "--source",
1115
+ options.source,
1116
+ "--strategy",
1117
+ options.strategy
1118
+ ];
1119
+ if (options.chunkSize) {
1120
+ args.push("--chunk-size", options.chunkSize);
1121
+ }
1122
+ const result = await runPythonModule({
1123
+ module: "rag_forge_core.cli",
1124
+ args
1125
+ });
1126
+ if (result.exitCode !== 0) {
1127
+ let errorMessage = result.stderr || "Unknown error";
1128
+ try {
1129
+ const parsed = JSON.parse(result.stdout);
1130
+ if (parsed.error) errorMessage = parsed.error;
1131
+ } catch {
1132
+ }
1133
+ spinner.fail("Chunk preview failed");
1134
+ logger.error(errorMessage);
1135
+ process.exit(1);
1136
+ }
1137
+ const output = JSON.parse(
1138
+ result.stdout
1139
+ );
1140
+ if (!output.success) {
1141
+ spinner.fail("Chunk preview failed");
1142
+ logger.error(output.error ?? "Unknown error");
1143
+ process.exit(1);
1144
+ }
1145
+ spinner.succeed(
1146
+ `${String(output.total_chunks)} chunks (${output.strategy}, target ${String(output.chunk_size)} tokens)`
1147
+ );
1148
+ console.log("");
1149
+ logger.info("Stats:");
1150
+ logger.info(
1151
+ ` Avg size: ${String(output.stats.avg_chunk_size)} tokens`
1152
+ );
1153
+ logger.info(
1154
+ ` Min size: ${String(output.stats.min_chunk_size)} tokens`
1155
+ );
1156
+ logger.info(
1157
+ ` Max size: ${String(output.stats.max_chunk_size)} tokens`
1158
+ );
1159
+ logger.info(
1160
+ ` Total tokens: ${String(output.stats.total_tokens)}`
1161
+ );
1162
+ if (output.samples.length > 0) {
1163
+ console.log("");
1164
+ logger.info("Sample chunks:");
1165
+ for (const sample of output.samples) {
1166
+ logger.info(
1167
+ ` [${String(sample.index)}] ${sample.source}: ${sample.preview}`
1168
+ );
1169
+ }
1170
+ }
1171
+ } catch (error) {
1172
+ spinner.fail("Chunk preview failed");
1173
+ const message = error instanceof Error ? error.message : "Unknown error";
1174
+ logger.error(message);
1175
+ process.exit(1);
1176
+ }
1177
+ }
1178
+ );
1179
+ }
1180
+
1181
+ // src/commands/n8n.ts
1182
+ import ora14 from "ora";
1183
+ function registerN8nCommand(program2) {
1184
+ program2.command("n8n").option(
1185
+ "-o, --output <file>",
1186
+ "Output file path",
1187
+ "n8n-workflow.json"
1188
+ ).option(
1189
+ "--mcp-url <url>",
1190
+ "MCP server SSE endpoint",
1191
+ "http://localhost:3100/sse"
1192
+ ).description("Export pipeline configuration as an importable n8n workflow").action(async (options) => {
1193
+ const spinner = ora14("Generating n8n workflow...").start();
1194
+ try {
1195
+ const args = [
1196
+ "n8n-export",
1197
+ "--output",
1198
+ options.output,
1199
+ "--mcp-url",
1200
+ options.mcpUrl
1201
+ ];
1202
+ const result = await runPythonModule({
1203
+ module: "rag_forge_core.cli",
1204
+ args
1205
+ });
1206
+ if (result.exitCode !== 0) {
1207
+ spinner.fail("n8n export failed");
1208
+ logger.error(result.stderr || "Unknown error");
1209
+ process.exit(1);
1210
+ }
1211
+ const output = JSON.parse(
1212
+ result.stdout
1213
+ );
1214
+ if (!output.success) {
1215
+ spinner.fail("n8n export failed");
1216
+ logger.error(output.error ?? "Unknown error");
1217
+ process.exit(1);
1218
+ }
1219
+ spinner.succeed(
1220
+ `Exported n8n workflow with ${String(output.nodes)} nodes`
1221
+ );
1222
+ logger.info(` Output: ${output.output_path}`);
1223
+ logger.info("");
1224
+ logger.info("Import this file into n8n via:");
1225
+ logger.info(" 1. Open n8n dashboard");
1226
+ logger.info(" 2. Click 'Import from File'");
1227
+ logger.info(` 3. Select ${output.output_path}`);
1228
+ } catch (error) {
1229
+ spinner.fail("n8n export failed");
1230
+ const message = error instanceof Error ? error.message : "Unknown error";
1231
+ logger.error(message);
1232
+ process.exit(1);
1233
+ }
1234
+ });
1235
+ }
1236
+
1237
+ // src/index.ts
1238
+ var program = new Command();
1239
+ program.name("rag-forge").description(
1240
+ "Framework-agnostic CLI toolkit for production-grade RAG pipelines with evaluation baked in"
1241
+ ).version("0.1.0");
1242
+ registerInitCommand(program);
1243
+ registerIndexCommand(program);
1244
+ registerAuditCommand(program);
1245
+ registerQueryCommand(program);
1246
+ registerServeCommand(program);
1247
+ registerDriftCommand(program);
1248
+ registerCostCommand(program);
1249
+ registerGoldenCommand(program);
1250
+ registerAssessCommand(program);
1251
+ registerGuardrailsCommand(program);
1252
+ registerReportCommand(program);
1253
+ registerCacheCommand(program);
1254
+ registerInspectCommand(program);
1255
+ registerAddCommand(program);
1256
+ registerParseCommand(program);
1257
+ registerChunkCommand(program);
1258
+ registerN8nCommand(program);
1259
+ program.parse();
1260
+ //# sourceMappingURL=index.js.map