@devilsdev/rag-pipeline-utils 2.3.0 → 2.4.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 (160) hide show
  1. package/README.md +293 -361
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +20 -8
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Shared CLI utility functions
3
+ */
4
+
5
+ const pkg = require("../../../package.json");
6
+
7
+ /**
8
+ * Create a simple progress bar for CLI output
9
+ * @param {number} percentage - Progress percentage (0-100)
10
+ * @param {number} width - Width of progress bar in characters
11
+ * @returns {string} Progress bar string
12
+ */
13
+ function createProgressBar(percentage, width = 20) {
14
+ const filled = Math.round((percentage / 100) * width);
15
+ const empty = width - filled;
16
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
17
+ return `[${bar}]`;
18
+ }
19
+
20
+ /**
21
+ * Custom parser for positive integers
22
+ */
23
+ function parsePositiveInteger(value) {
24
+ const parsed = parseInt(value, 10);
25
+ if (isNaN(parsed) || parsed <= 0) {
26
+ throw new Error(`Invalid value '${value}'. Expected positive integer.`);
27
+ }
28
+ return parsed;
29
+ }
30
+
31
+ /**
32
+ * Generate shell completion script
33
+ */
34
+ function generateCompletionScript(shell) {
35
+ switch (shell) {
36
+ case "bash":
37
+ return `# RAG Pipeline bash completion
38
+ _rag_pipeline_completions() {
39
+ COMPREPLY=($(compgen -W "init doctor ingest query plugin config info validate completion" -- "\${COMP_WORDS[COMP_CWORD]}"))
40
+ }
41
+ complete -F _rag_pipeline_completions rag-pipeline`;
42
+
43
+ case "zsh":
44
+ return `# RAG Pipeline zsh completion
45
+ #compdef rag-pipeline
46
+ _rag_pipeline() {
47
+ _arguments \\
48
+ '1:command:(init doctor ingest query plugin config info validate completion)'
49
+ }
50
+ _rag_pipeline "$@"`;
51
+
52
+ case "fish":
53
+ return `# RAG Pipeline fish completion
54
+ complete -c rag-pipeline -n '__fish_use_subcommand' -a 'init doctor ingest query plugin config info validate completion'`;
55
+
56
+ default:
57
+ return `# Completion not available for ${shell}`;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get version information from package.json
63
+ */
64
+ function getVersion() {
65
+ return pkg.version;
66
+ }
67
+
68
+ /**
69
+ * Get extended help text
70
+ */
71
+ function getExtendedHelp() {
72
+ return `
73
+ Examples:
74
+ rag-pipeline init --interactive Initialize with wizard
75
+ rag-pipeline doctor --auto-fix Diagnose and fix issues
76
+ rag-pipeline plugin search openai Search for plugins
77
+ rag-pipeline config show plugins Show plugin configuration
78
+
79
+ For more information, visit: https://github.com/DevilsDev/rag-pipeline-utils
80
+ `;
81
+ }
82
+
83
+ module.exports = {
84
+ createProgressBar,
85
+ parsePositiveInteger,
86
+ generateCompletionScript,
87
+ getVersion,
88
+ getExtendedHelp,
89
+ };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Configuration Step
3
+ * Handles general settings configuration (caching, timeout, pipeline stages)
4
+ */
5
+
6
+ /**
7
+ * Configure general settings
8
+ * @param {object} config - Current wizard configuration state
9
+ * @param {object} inquirer - Inquirer instance for prompting
10
+ * @returns {Promise<object>} Updated configuration state
11
+ */
12
+ async function configureSettings(config, inquirer) {
13
+ console.log("⚙️ General Settings\n");
14
+ // eslint-disable-line no-console
15
+
16
+ const answers = await inquirer.prompt([
17
+ {
18
+ _type: "confirm",
19
+ name: "enableCaching",
20
+ message: "Enable result caching?",
21
+ default: false,
22
+ },
23
+ {
24
+ _type: "number",
25
+ name: "cacheSize",
26
+ message: "Cache size (number of entries):",
27
+ default: 1000,
28
+ when: (answers) => answers.enableCaching,
29
+ },
30
+ {
31
+ _type: "number",
32
+ name: "cacheTtl",
33
+ message: "Cache TTL (seconds):",
34
+ default: 3600,
35
+ when: (answers) => answers.enableCaching,
36
+ },
37
+ {
38
+ _type: "number",
39
+ name: "timeout",
40
+ message: "Pipeline timeout (milliseconds):",
41
+ default: 30000,
42
+ },
43
+ ]);
44
+
45
+ if (answers.enableCaching) {
46
+ config.performance = config.performance || {};
47
+ config.performance.caching = {
48
+ enabled: true,
49
+ maxSize: answers.cacheSize,
50
+ ttl: answers.cacheTtl,
51
+ };
52
+ }
53
+
54
+ if (answers.timeout !== 30000) {
55
+ config.pipeline = config.pipeline || {};
56
+ config.pipeline.timeout = answers.timeout;
57
+ }
58
+
59
+ console.log("✅ General settings configured\n");
60
+ // eslint-disable-line no-console
61
+
62
+ return config;
63
+ }
64
+
65
+ /**
66
+ * Configure pipeline stages
67
+ * @param {object} config - Current wizard configuration state
68
+ * @param {object} inquirer - Inquirer instance for prompting
69
+ * @returns {Promise<object>} Updated configuration state
70
+ */
71
+ async function configurePipeline(config, inquirer) {
72
+ console.log("🔄 Pipeline Configuration\n");
73
+ // eslint-disable-line no-console
74
+
75
+ const availableStages = Object.keys(config.plugins).filter(
76
+ (_type) => Object.keys(config.plugins[_type]).length > 0,
77
+ );
78
+
79
+ const answers = await inquirer.prompt([
80
+ {
81
+ _type: "checkbox",
82
+ name: "stages",
83
+ message: "Select pipeline stages (in order):",
84
+ choices: [
85
+ {
86
+ name: "Document Loading",
87
+ value: "loader",
88
+ checked: availableStages.includes("loader"),
89
+ },
90
+ {
91
+ name: "Text Embedding",
92
+ value: "embedder",
93
+ checked: availableStages.includes("embedder"),
94
+ },
95
+ {
96
+ name: "Vector Retrieval",
97
+ value: "retriever",
98
+ checked: availableStages.includes("retriever"),
99
+ },
100
+ {
101
+ name: "Language Model",
102
+ value: "llm",
103
+ checked: availableStages.includes("llm"),
104
+ },
105
+ {
106
+ name: "Result Reranking",
107
+ value: "reranker",
108
+ checked: availableStages.includes("reranker"),
109
+ },
110
+ ],
111
+ validate: (input) =>
112
+ input.length > 0 || "At least one stage must be selected",
113
+ },
114
+ {
115
+ _type: "confirm",
116
+ name: "enableRetries",
117
+ message: "Enable automatic retries on failures?",
118
+ default: true,
119
+ },
120
+ {
121
+ _type: "number",
122
+ name: "maxRetries",
123
+ message: "Maximum retry attempts:",
124
+ default: 3,
125
+ when: (answers) => answers.enableRetries,
126
+ },
127
+ ]);
128
+
129
+ config.pipeline = {
130
+ stages: answers.stages,
131
+ ...config.pipeline,
132
+ };
133
+
134
+ if (answers.enableRetries) {
135
+ config.pipeline.retries = {
136
+ enabled: true,
137
+ maxAttempts: answers.maxRetries,
138
+ backoff: "exponential",
139
+ };
140
+ }
141
+
142
+ console.log("✅ Pipeline configuration complete\n");
143
+ // eslint-disable-line no-console
144
+
145
+ return config;
146
+ }
147
+
148
+ module.exports = { configureSettings, configurePipeline };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Observability Step
3
+ * Handles observability settings configuration (logging, tracing, metrics)
4
+ */
5
+
6
+ /**
7
+ * Configure observability settings
8
+ * @param {object} config - Current wizard configuration state
9
+ * @param {object} inquirer - Inquirer instance for prompting
10
+ * @returns {Promise<object>} Updated configuration state
11
+ */
12
+ async function configureObservability(config, inquirer) {
13
+ console.log("📊 Observability Settings\n");
14
+ // eslint-disable-line no-console
15
+
16
+ const answers = await inquirer.prompt([
17
+ {
18
+ _type: "list",
19
+ name: "logLevel",
20
+ message: "Log level:",
21
+ choices: [
22
+ { name: "Debug (verbose)", value: "debug" },
23
+ { name: "Info (default)", value: "info" },
24
+ { name: "Warning (minimal)", value: "warn" },
25
+ { name: "Error (errors only)", value: "error" },
26
+ ],
27
+ default: "info",
28
+ },
29
+ {
30
+ _type: "confirm",
31
+ name: "enableTracing",
32
+ message: "Enable distributed tracing?",
33
+ default: false,
34
+ },
35
+ {
36
+ _type: "confirm",
37
+ name: "enableMetrics",
38
+ message: "Enable metrics collection?",
39
+ default: false,
40
+ },
41
+ {
42
+ _type: "input",
43
+ name: "exportUrl",
44
+ message: "Metrics/tracing export URL (optional):",
45
+ when: (answers) => answers.enableTracing || answers.enableMetrics,
46
+ },
47
+ ]);
48
+
49
+ config.observability = {
50
+ logging: {
51
+ level: answers.logLevel,
52
+ structured: true,
53
+ events: answers.enableTracing,
54
+ },
55
+ };
56
+
57
+ if (answers.enableTracing) {
58
+ config.observability.tracing = {
59
+ enabled: true,
60
+ exportUrl: answers.exportUrl || undefined,
61
+ sampleRate: 1,
62
+ };
63
+ }
64
+
65
+ if (answers.enableMetrics) {
66
+ config.observability.metrics = {
67
+ enabled: true,
68
+ exportUrl: answers.exportUrl || undefined,
69
+ interval: 60000,
70
+ };
71
+ }
72
+
73
+ console.log("✅ Observability settings configured\n");
74
+ // eslint-disable-line no-console
75
+
76
+ return config;
77
+ }
78
+
79
+ module.exports = { configureObservability };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Performance Step
3
+ * Handles performance settings configuration (parallel processing, streaming)
4
+ */
5
+
6
+ /**
7
+ * Configure performance settings
8
+ * @param {object} config - Current wizard configuration state
9
+ * @param {object} inquirer - Inquirer instance for prompting
10
+ * @returns {Promise<object>} Updated configuration state
11
+ */
12
+ async function configurePerformance(config, inquirer) {
13
+ console.log("⚡ Performance Settings\n");
14
+ // eslint-disable-line no-console
15
+
16
+ const answers = await inquirer.prompt([
17
+ {
18
+ _type: "confirm",
19
+ name: "enableParallel",
20
+ message: "Enable parallel processing?",
21
+ default: false,
22
+ },
23
+ {
24
+ _type: "number",
25
+ name: "maxConcurrency",
26
+ message: "Maximum concurrent operations:",
27
+ default: 3,
28
+ when: (answers) => answers.enableParallel,
29
+ },
30
+ {
31
+ _type: "number",
32
+ name: "batchSize",
33
+ message: "Batch size for parallel processing:",
34
+ default: 10,
35
+ when: (answers) => answers.enableParallel,
36
+ },
37
+ {
38
+ _type: "confirm",
39
+ name: "enableStreaming",
40
+ message: "Enable streaming for large documents?",
41
+ default: false,
42
+ },
43
+ {
44
+ _type: "number",
45
+ name: "maxMemoryMB",
46
+ message: "Maximum memory usage (MB):",
47
+ default: 512,
48
+ when: (answers) => answers.enableStreaming,
49
+ },
50
+ ]);
51
+
52
+ config.performance = config.performance || {};
53
+
54
+ if (answers.enableParallel) {
55
+ config.performance.parallel = {
56
+ enabled: true,
57
+ maxConcurrency: answers.maxConcurrency,
58
+ batchSize: answers.batchSize,
59
+ };
60
+ }
61
+
62
+ if (answers.enableStreaming) {
63
+ config.performance.streaming = {
64
+ enabled: true,
65
+ maxMemoryMB: answers.maxMemoryMB,
66
+ bufferSize: 100,
67
+ };
68
+ }
69
+
70
+ console.log("✅ Performance settings configured\n");
71
+ // eslint-disable-line no-console
72
+
73
+ return config;
74
+ }
75
+
76
+ module.exports = { configurePerformance };
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Plugin Selection Step
3
+ * Handles plugin selection, custom plugin configuration, and plugin settings
4
+ */
5
+
6
+ /**
7
+ * Select plugins for each type
8
+ * @param {object} config - Current wizard configuration state
9
+ * @param {object} inquirer - Inquirer instance for prompting
10
+ * @param {object} context - Wizard context with registry and helper methods
11
+ * @param {object|null} context.registry - Plugin registry data
12
+ * @param {Function} context.getAvailablePlugins - Function to get available plugins for a type
13
+ * @param {Function} context.getPluginInfo - Function to get plugin info
14
+ * @param {Function} context.loadRegistry - Function to load plugin registry
15
+ * @returns {Promise<object>} Updated configuration state
16
+ */
17
+ async function selectPlugins(config, inquirer, context) {
18
+ console.log("🔌 Plugin Selection\n");
19
+ // eslint-disable-line no-console
20
+
21
+ // Load registry for plugin suggestions
22
+ try {
23
+ context.registry = await context.loadRegistry();
24
+ } catch (error) {
25
+ console.warn(
26
+ "⚠️ Could not load plugin registry. Using built-in _options.",
27
+ );
28
+ // eslint-disable-line no-console
29
+ }
30
+
31
+ const pluginTypes = [
32
+ { key: "loader", name: "Document Loader", required: true },
33
+ { key: "embedder", name: "Text Embedder", required: true },
34
+ { key: "retriever", name: "Vector Retriever", required: true },
35
+ { key: "llm", name: "Language Model", required: true },
36
+ { key: "reranker", name: "Result Reranker", required: false },
37
+ ];
38
+
39
+ for (const pluginType of pluginTypes) {
40
+ await selectPluginForType(config, inquirer, context, pluginType);
41
+ }
42
+
43
+ console.log("✅ Plugin selection complete\n");
44
+ // eslint-disable-line no-console
45
+
46
+ return config;
47
+ }
48
+
49
+ /**
50
+ * Select plugin for a specific type
51
+ * @param {object} config - Current wizard configuration state
52
+ * @param {object} inquirer - Inquirer instance for prompting
53
+ * @param {object} context - Wizard context with helper methods
54
+ * @param {object} pluginType - Plugin type configuration
55
+ * @returns {Promise<void>}
56
+ */
57
+ async function selectPluginForType(config, inquirer, context, pluginType) {
58
+ const availablePlugins = context.getAvailablePlugins(pluginType.key);
59
+
60
+ if (availablePlugins.length === 0 && pluginType.required) {
61
+ console.log(
62
+ `⚠️ No ${pluginType.name} plugins available. You'll need to configure this manually.`,
63
+ );
64
+ // eslint-disable-line no-console
65
+ return;
66
+ }
67
+
68
+ const choices = [
69
+ ...availablePlugins.map((plugin) => ({
70
+ name: `${plugin.name} - ${plugin.description}`,
71
+ value: plugin.name,
72
+ short: plugin.name,
73
+ })),
74
+ { name: "Skip (configure later)", value: null },
75
+ { name: "Custom plugin", value: "custom" },
76
+ ];
77
+
78
+ const answer = await inquirer.prompt([
79
+ {
80
+ _type: "list",
81
+ name: "plugin",
82
+ message: `Select ${pluginType.name}:`,
83
+ choices,
84
+ when: () => !pluginType.required || availablePlugins.length > 0,
85
+ },
86
+ ]);
87
+
88
+ if (!answer.plugin) {
89
+ return; // Skip
90
+ }
91
+
92
+ if (answer.plugin === "custom") {
93
+ await configureCustomPlugin(config, inquirer, pluginType.key);
94
+ } else {
95
+ await configureSelectedPlugin(
96
+ config,
97
+ inquirer,
98
+ context,
99
+ pluginType.key,
100
+ answer.plugin,
101
+ );
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Configure a custom plugin
107
+ * @param {object} config - Current wizard configuration state
108
+ * @param {object} inquirer - Inquirer instance for prompting
109
+ * @param {string} pluginType - Plugin type key
110
+ * @returns {Promise<void>}
111
+ */
112
+ async function configureCustomPlugin(config, inquirer, pluginType) {
113
+ const answers = await inquirer.prompt([
114
+ {
115
+ _type: "input",
116
+ name: "name",
117
+ message: "Plugin name:",
118
+ validate: (input) => input.length > 0 || "Plugin name is required",
119
+ },
120
+ {
121
+ _type: "list",
122
+ name: "source",
123
+ message: "Plugin source:",
124
+ choices: [
125
+ { name: "Registry", value: "registry" },
126
+ { name: "Local file", value: "local" },
127
+ { name: "Git repository", value: "git" },
128
+ { name: "NPM package", value: "npm" },
129
+ ],
130
+ default: "registry",
131
+ },
132
+ {
133
+ _type: "input",
134
+ name: "version",
135
+ message: "Version:",
136
+ default: "latest",
137
+ when: (answers) =>
138
+ answers.source === "registry" || answers.source === "npm",
139
+ },
140
+ {
141
+ _type: "input",
142
+ name: "path",
143
+ message: "Local path:",
144
+ when: (answers) => answers.source === "local",
145
+ },
146
+ {
147
+ _type: "input",
148
+ name: "url",
149
+ message: "Repository URL:",
150
+ when: (answers) => answers.source === "git",
151
+ },
152
+ ]);
153
+
154
+ const pluginSpec = {
155
+ name: answers.name,
156
+ source: answers.source,
157
+ };
158
+
159
+ if (answers.version) pluginSpec.version = answers.version;
160
+ if (answers.path) pluginSpec.path = answers.path;
161
+ if (answers.url) pluginSpec.url = answers.url;
162
+
163
+ config.plugins[pluginType][answers.name] = pluginSpec;
164
+ }
165
+
166
+ /**
167
+ * Configure a selected plugin from registry
168
+ * @param {object} config - Current wizard configuration state
169
+ * @param {object} inquirer - Inquirer instance for prompting
170
+ * @param {object} context - Wizard context with helper methods
171
+ * @param {string} pluginType - Plugin type key
172
+ * @param {string} pluginName - Plugin name
173
+ * @returns {Promise<void>}
174
+ */
175
+ async function configureSelectedPlugin(
176
+ config,
177
+ inquirer,
178
+ context,
179
+ pluginType,
180
+ pluginName,
181
+ ) {
182
+ const plugin = context.getPluginInfo(pluginType, pluginName);
183
+
184
+ if (!plugin) {
185
+ config.plugins[pluginType][pluginName] = "latest";
186
+ return;
187
+ }
188
+
189
+ const versions = Object.keys(plugin.versions || {}).slice(0, 5);
190
+ const versionChoices = [
191
+ { name: "Latest stable", value: "latest" },
192
+ { name: "Latest beta", value: "beta" },
193
+ ...versions.map((v) => ({ name: v, value: v })),
194
+ { name: "Custom version", value: "custom" },
195
+ ];
196
+
197
+ const versionAnswer = await inquirer.prompt([
198
+ {
199
+ _type: "list",
200
+ name: "version",
201
+ message: `Select version for ${pluginName}:`,
202
+ choices: versionChoices,
203
+ default: "latest",
204
+ },
205
+ {
206
+ _type: "input",
207
+ name: "customVersion",
208
+ message: "Enter version:",
209
+ when: (answers) => answers.version === "custom",
210
+ },
211
+ ]);
212
+
213
+ const version = versionAnswer.customVersion || versionAnswer.version;
214
+
215
+ // Check if plugin needs configuration
216
+ const needsConfig =
217
+ plugin.metadata?._config &&
218
+ Object.keys(plugin.metadata._config.properties || {}).length > 0;
219
+
220
+ if (needsConfig) {
221
+ const configAnswer = await inquirer.prompt([
222
+ {
223
+ _type: "confirm",
224
+ name: "configure",
225
+ message: `Configure ${pluginName} settings?`,
226
+ default: false,
227
+ },
228
+ ]);
229
+
230
+ if (configAnswer.configure) {
231
+ const pluginConfig = await configurePluginSettings(plugin, inquirer);
232
+ config.plugins[pluginType][pluginName] = {
233
+ name: pluginName,
234
+ version,
235
+ _config: pluginConfig,
236
+ };
237
+ } else {
238
+ config.plugins[pluginType][pluginName] = {
239
+ name: pluginName,
240
+ version,
241
+ };
242
+ }
243
+ } else {
244
+ config.plugins[pluginType][pluginName] = version;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Configure plugin-specific settings
250
+ * @param {object} plugin - Plugin information
251
+ * @param {object} inquirer - Inquirer instance for prompting
252
+ * @returns {Promise<object>} Plugin configuration
253
+ */
254
+ async function configurePluginSettings(plugin, inquirer) {
255
+ const _config = {};
256
+ const schema = plugin.metadata?._config?.properties || {};
257
+
258
+ console.log(`\n⚙️ Configuring ${plugin.metadata.name}...\n`);
259
+ // eslint-disable-line no-console
260
+
261
+ for (const [key, property] of Object.entries(schema)) {
262
+ const question = {
263
+ name: key,
264
+ message: `${key}${property.description ? ` (${property.description})` : ""}:`,
265
+ };
266
+
267
+ if (property._type === "boolean") {
268
+ question._type = "confirm";
269
+ question.default = property.default || false;
270
+ } else if (property.enum) {
271
+ question._type = "list";
272
+ question.choices = property.enum;
273
+ question.default = property.default;
274
+ } else if (property._type === "number") {
275
+ question._type = "number";
276
+ question.default = property.default;
277
+ } else {
278
+ question._type = "input";
279
+ question.default = property.default || "";
280
+ }
281
+
282
+ const answer = await inquirer.prompt([question]);
283
+ if (answer[key] !== "" && answer[key] !== undefined) {
284
+ _config[key] = answer[key];
285
+ }
286
+ }
287
+
288
+ return _config;
289
+ }
290
+
291
+ module.exports = {
292
+ selectPlugins,
293
+ selectPluginForType,
294
+ configureCustomPlugin,
295
+ configureSelectedPlugin,
296
+ configurePluginSettings,
297
+ };