viberag 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.
Files changed (151) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +219 -0
  3. package/dist/cli/__tests__/mcp-setup.test.d.ts +6 -0
  4. package/dist/cli/__tests__/mcp-setup.test.js +597 -0
  5. package/dist/cli/app.d.ts +2 -0
  6. package/dist/cli/app.js +238 -0
  7. package/dist/cli/commands/handlers.d.ts +57 -0
  8. package/dist/cli/commands/handlers.js +231 -0
  9. package/dist/cli/commands/index.d.ts +2 -0
  10. package/dist/cli/commands/index.js +2 -0
  11. package/dist/cli/commands/mcp-setup.d.ts +107 -0
  12. package/dist/cli/commands/mcp-setup.js +509 -0
  13. package/dist/cli/commands/useRagCommands.d.ts +23 -0
  14. package/dist/cli/commands/useRagCommands.js +180 -0
  15. package/dist/cli/components/CleanWizard.d.ts +17 -0
  16. package/dist/cli/components/CleanWizard.js +169 -0
  17. package/dist/cli/components/InitWizard.d.ts +20 -0
  18. package/dist/cli/components/InitWizard.js +370 -0
  19. package/dist/cli/components/McpSetupWizard.d.ts +37 -0
  20. package/dist/cli/components/McpSetupWizard.js +387 -0
  21. package/dist/cli/components/SearchResultsDisplay.d.ts +13 -0
  22. package/dist/cli/components/SearchResultsDisplay.js +130 -0
  23. package/dist/cli/components/WelcomeBanner.d.ts +10 -0
  24. package/dist/cli/components/WelcomeBanner.js +26 -0
  25. package/dist/cli/components/index.d.ts +1 -0
  26. package/dist/cli/components/index.js +1 -0
  27. package/dist/cli/data/mcp-editors.d.ts +80 -0
  28. package/dist/cli/data/mcp-editors.js +270 -0
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.js +26 -0
  31. package/dist/cli-bundle.cjs +5269 -0
  32. package/dist/common/commands/terminalSetup.d.ts +2 -0
  33. package/dist/common/commands/terminalSetup.js +144 -0
  34. package/dist/common/components/CommandSuggestions.d.ts +9 -0
  35. package/dist/common/components/CommandSuggestions.js +20 -0
  36. package/dist/common/components/StaticWithResize.d.ts +23 -0
  37. package/dist/common/components/StaticWithResize.js +62 -0
  38. package/dist/common/components/StatusBar.d.ts +8 -0
  39. package/dist/common/components/StatusBar.js +64 -0
  40. package/dist/common/components/TextInput.d.ts +12 -0
  41. package/dist/common/components/TextInput.js +239 -0
  42. package/dist/common/components/index.d.ts +3 -0
  43. package/dist/common/components/index.js +3 -0
  44. package/dist/common/hooks/index.d.ts +4 -0
  45. package/dist/common/hooks/index.js +4 -0
  46. package/dist/common/hooks/useCommandHistory.d.ts +7 -0
  47. package/dist/common/hooks/useCommandHistory.js +51 -0
  48. package/dist/common/hooks/useCtrlC.d.ts +9 -0
  49. package/dist/common/hooks/useCtrlC.js +40 -0
  50. package/dist/common/hooks/useKittyKeyboard.d.ts +10 -0
  51. package/dist/common/hooks/useKittyKeyboard.js +26 -0
  52. package/dist/common/hooks/useStaticOutputBuffer.d.ts +31 -0
  53. package/dist/common/hooks/useStaticOutputBuffer.js +58 -0
  54. package/dist/common/hooks/useTerminalResize.d.ts +28 -0
  55. package/dist/common/hooks/useTerminalResize.js +51 -0
  56. package/dist/common/hooks/useTextBuffer.d.ts +13 -0
  57. package/dist/common/hooks/useTextBuffer.js +165 -0
  58. package/dist/common/index.d.ts +13 -0
  59. package/dist/common/index.js +17 -0
  60. package/dist/common/types.d.ts +162 -0
  61. package/dist/common/types.js +1 -0
  62. package/dist/mcp/index.d.ts +12 -0
  63. package/dist/mcp/index.js +66 -0
  64. package/dist/mcp/server.d.ts +25 -0
  65. package/dist/mcp/server.js +837 -0
  66. package/dist/mcp/watcher.d.ts +86 -0
  67. package/dist/mcp/watcher.js +334 -0
  68. package/dist/rag/__tests__/grammar-smoke.test.d.ts +9 -0
  69. package/dist/rag/__tests__/grammar-smoke.test.js +161 -0
  70. package/dist/rag/__tests__/helpers.d.ts +30 -0
  71. package/dist/rag/__tests__/helpers.js +67 -0
  72. package/dist/rag/__tests__/merkle.test.d.ts +5 -0
  73. package/dist/rag/__tests__/merkle.test.js +161 -0
  74. package/dist/rag/__tests__/metadata-extraction.test.d.ts +10 -0
  75. package/dist/rag/__tests__/metadata-extraction.test.js +202 -0
  76. package/dist/rag/__tests__/multi-language.test.d.ts +13 -0
  77. package/dist/rag/__tests__/multi-language.test.js +535 -0
  78. package/dist/rag/__tests__/rag.test.d.ts +10 -0
  79. package/dist/rag/__tests__/rag.test.js +311 -0
  80. package/dist/rag/__tests__/search-exhaustive.test.d.ts +9 -0
  81. package/dist/rag/__tests__/search-exhaustive.test.js +87 -0
  82. package/dist/rag/__tests__/search-filters.test.d.ts +10 -0
  83. package/dist/rag/__tests__/search-filters.test.js +250 -0
  84. package/dist/rag/__tests__/search-modes.test.d.ts +8 -0
  85. package/dist/rag/__tests__/search-modes.test.js +133 -0
  86. package/dist/rag/config/index.d.ts +61 -0
  87. package/dist/rag/config/index.js +111 -0
  88. package/dist/rag/constants.d.ts +41 -0
  89. package/dist/rag/constants.js +57 -0
  90. package/dist/rag/embeddings/fastembed.d.ts +62 -0
  91. package/dist/rag/embeddings/fastembed.js +124 -0
  92. package/dist/rag/embeddings/gemini.d.ts +26 -0
  93. package/dist/rag/embeddings/gemini.js +116 -0
  94. package/dist/rag/embeddings/index.d.ts +10 -0
  95. package/dist/rag/embeddings/index.js +9 -0
  96. package/dist/rag/embeddings/local-4b.d.ts +28 -0
  97. package/dist/rag/embeddings/local-4b.js +51 -0
  98. package/dist/rag/embeddings/local.d.ts +29 -0
  99. package/dist/rag/embeddings/local.js +119 -0
  100. package/dist/rag/embeddings/mistral.d.ts +22 -0
  101. package/dist/rag/embeddings/mistral.js +85 -0
  102. package/dist/rag/embeddings/openai.d.ts +22 -0
  103. package/dist/rag/embeddings/openai.js +85 -0
  104. package/dist/rag/embeddings/types.d.ts +37 -0
  105. package/dist/rag/embeddings/types.js +1 -0
  106. package/dist/rag/gitignore/index.d.ts +57 -0
  107. package/dist/rag/gitignore/index.js +178 -0
  108. package/dist/rag/index.d.ts +15 -0
  109. package/dist/rag/index.js +25 -0
  110. package/dist/rag/indexer/chunker.d.ts +129 -0
  111. package/dist/rag/indexer/chunker.js +1352 -0
  112. package/dist/rag/indexer/index.d.ts +6 -0
  113. package/dist/rag/indexer/index.js +6 -0
  114. package/dist/rag/indexer/indexer.d.ts +73 -0
  115. package/dist/rag/indexer/indexer.js +356 -0
  116. package/dist/rag/indexer/types.d.ts +68 -0
  117. package/dist/rag/indexer/types.js +47 -0
  118. package/dist/rag/logger/index.d.ts +20 -0
  119. package/dist/rag/logger/index.js +75 -0
  120. package/dist/rag/manifest/index.d.ts +50 -0
  121. package/dist/rag/manifest/index.js +97 -0
  122. package/dist/rag/merkle/diff.d.ts +26 -0
  123. package/dist/rag/merkle/diff.js +95 -0
  124. package/dist/rag/merkle/hash.d.ts +34 -0
  125. package/dist/rag/merkle/hash.js +165 -0
  126. package/dist/rag/merkle/index.d.ts +68 -0
  127. package/dist/rag/merkle/index.js +298 -0
  128. package/dist/rag/merkle/node.d.ts +51 -0
  129. package/dist/rag/merkle/node.js +69 -0
  130. package/dist/rag/search/filters.d.ts +21 -0
  131. package/dist/rag/search/filters.js +100 -0
  132. package/dist/rag/search/fts.d.ts +32 -0
  133. package/dist/rag/search/fts.js +61 -0
  134. package/dist/rag/search/hybrid.d.ts +17 -0
  135. package/dist/rag/search/hybrid.js +58 -0
  136. package/dist/rag/search/index.d.ts +89 -0
  137. package/dist/rag/search/index.js +367 -0
  138. package/dist/rag/search/types.d.ts +130 -0
  139. package/dist/rag/search/types.js +4 -0
  140. package/dist/rag/search/vector.d.ts +25 -0
  141. package/dist/rag/search/vector.js +44 -0
  142. package/dist/rag/storage/index.d.ts +92 -0
  143. package/dist/rag/storage/index.js +287 -0
  144. package/dist/rag/storage/lancedb-native.d.ts +7 -0
  145. package/dist/rag/storage/lancedb-native.js +10 -0
  146. package/dist/rag/storage/schema.d.ts +23 -0
  147. package/dist/rag/storage/schema.js +50 -0
  148. package/dist/rag/storage/types.d.ts +100 -0
  149. package/dist/rag/storage/types.js +68 -0
  150. package/package.json +67 -0
  151. package/scripts/check-node-version.js +37 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Clean Wizard Component
3
+ *
4
+ * Interactive wizard for cleaning up VibeRAG from a project,
5
+ * including MCP server configurations.
6
+ */
7
+ import React from 'react';
8
+ type Props = {
9
+ projectRoot: string;
10
+ viberagDir: string;
11
+ onComplete: () => void;
12
+ onCancel: () => void;
13
+ /** For outputting status messages */
14
+ addOutput: (type: 'system' | 'user', content: string) => void;
15
+ };
16
+ export declare function CleanWizard({ projectRoot, viberagDir, onComplete, onCancel, addOutput, }: Props): React.ReactElement;
17
+ export default CleanWizard;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Clean Wizard Component
3
+ *
4
+ * Interactive wizard for cleaning up VibeRAG from a project,
5
+ * including MCP server configurations.
6
+ */
7
+ import React, { useState, useEffect, useCallback } from 'react';
8
+ import { Box, Text, useInput } from 'ink';
9
+ import SelectInput from 'ink-select-input';
10
+ import { EDITORS } from '../data/mcp-editors.js';
11
+ import { findConfiguredEditors, removeViberagConfig, } from '../commands/mcp-setup.js';
12
+ const CONFIRM_ITEMS = [
13
+ { label: 'Yes, remove everything', value: 'continue' },
14
+ { label: 'Cancel', value: 'cancel' },
15
+ ];
16
+ const MCP_CLEANUP_ITEMS = [
17
+ { label: 'Yes, remove from MCP configs too (Recommended)', value: 'yes' },
18
+ { label: 'No, keep MCP configurations', value: 'no' },
19
+ ];
20
+ export function CleanWizard({ projectRoot, viberagDir, onComplete, onCancel, addOutput, }) {
21
+ const [step, setStep] = useState('confirm');
22
+ const [projectScopeEditors, setProjectScopeEditors] = useState([]);
23
+ const [globalScopeEditors, setGlobalScopeEditors] = useState([]);
24
+ const [mcpResults, setMcpResults] = useState([]);
25
+ const [viberagRemoved, setViberagRemoved] = useState(false);
26
+ // Handle Escape to cancel
27
+ useInput((input, key) => {
28
+ if (key.escape || (key.ctrl && input === 'c')) {
29
+ onCancel();
30
+ }
31
+ });
32
+ // Find configured editors on mount
33
+ useEffect(() => {
34
+ findConfiguredEditors(projectRoot).then(({ projectScope, globalScope }) => {
35
+ setProjectScopeEditors(projectScope);
36
+ setGlobalScopeEditors(globalScope);
37
+ });
38
+ }, [projectRoot]);
39
+ // Handle confirmation
40
+ const handleConfirm = useCallback((action) => {
41
+ if (action === 'cancel') {
42
+ onCancel();
43
+ return;
44
+ }
45
+ // If there are project-scope MCP configs, ask about cleanup
46
+ if (projectScopeEditors.length > 0) {
47
+ setStep('mcp-cleanup');
48
+ }
49
+ else {
50
+ // No MCP configs to clean, go straight to processing
51
+ setStep('processing');
52
+ performCleanup(false);
53
+ }
54
+ }, [projectScopeEditors, onCancel]);
55
+ // Perform the actual cleanup
56
+ const performCleanup = useCallback(async (cleanMcp) => {
57
+ const fs = await import('node:fs/promises');
58
+ // Remove .viberag directory
59
+ try {
60
+ await fs.rm(viberagDir, { recursive: true, force: true });
61
+ setViberagRemoved(true);
62
+ }
63
+ catch (error) {
64
+ addOutput('system', `Failed to remove ${viberagDir}: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ // Clean MCP configs if requested
67
+ if (cleanMcp) {
68
+ const results = [];
69
+ for (const editor of projectScopeEditors) {
70
+ const result = await removeViberagConfig(editor, projectRoot);
71
+ results.push(result);
72
+ }
73
+ setMcpResults(results);
74
+ }
75
+ setStep('summary');
76
+ }, [viberagDir, projectScopeEditors, projectRoot, addOutput]);
77
+ // Handle MCP cleanup choice
78
+ const handleMcpCleanup = useCallback((action) => {
79
+ setStep('processing');
80
+ performCleanup(action === 'yes');
81
+ }, [performCleanup]);
82
+ // Step: Confirm
83
+ if (step === 'confirm') {
84
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
85
+ React.createElement(Text, { bold: true, color: "yellow" }, "Clean VibeRAG"),
86
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
87
+ React.createElement(Text, null, "This will remove:"),
88
+ React.createElement(Text, { dimColor: true },
89
+ " \u2022 ",
90
+ viberagDir,
91
+ "/ (index and config)"),
92
+ projectScopeEditors.length > 0 && (React.createElement(Text, { dimColor: true },
93
+ ' ',
94
+ "\u2022 MCP configs (",
95
+ projectScopeEditors.map(e => e.name).join(', '),
96
+ ")"))),
97
+ globalScopeEditors.length > 0 && (React.createElement(Box, { marginTop: 1 },
98
+ React.createElement(Text, { color: "blue" },
99
+ "Note: Global MCP configs (",
100
+ globalScopeEditors.map(e => e.name).join(', '),
101
+ ")",
102
+ '\n',
103
+ "will NOT be modified. Remove manually if needed."))),
104
+ React.createElement(Box, { marginTop: 1 },
105
+ React.createElement(SelectInput, { items: CONFIRM_ITEMS, onSelect: item => handleConfirm(item.value) })),
106
+ React.createElement(Text, { dimColor: true }, "\u2191/\u2193 navigate, Enter select, Esc cancel")));
107
+ }
108
+ // Step: MCP Cleanup choice
109
+ if (step === 'mcp-cleanup') {
110
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
111
+ React.createElement(Text, { bold: true, color: "yellow" }, "Remove from MCP configs?"),
112
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
113
+ React.createElement(Text, null, "Found viberag in these project MCP configs:"),
114
+ projectScopeEditors.map(editor => (React.createElement(Text, { key: editor.id, dimColor: true },
115
+ ' ',
116
+ "\u2022 ",
117
+ editor.name,
118
+ " (",
119
+ editor.configPath,
120
+ ")")))),
121
+ React.createElement(Box, { marginTop: 1 },
122
+ React.createElement(SelectInput, { items: MCP_CLEANUP_ITEMS, onSelect: item => handleMcpCleanup(item.value) })),
123
+ React.createElement(Text, { dimColor: true }, "\u2191/\u2193 navigate, Enter select, Esc cancel")));
124
+ }
125
+ // Step: Processing
126
+ if (step === 'processing') {
127
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
128
+ React.createElement(Text, { bold: true, color: "yellow" }, "Cleaning..."),
129
+ React.createElement(Box, { marginTop: 1 },
130
+ React.createElement(Text, null, "Removing VibeRAG from project..."))));
131
+ }
132
+ // Step: Summary
133
+ if (step === 'summary') {
134
+ const successfulRemovals = mcpResults.filter(r => r.success);
135
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
136
+ React.createElement(Text, { bold: true, color: "green" }, "Clean Complete"),
137
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
138
+ viberagRemoved && (React.createElement(Text, null,
139
+ React.createElement(Text, { color: "green" }, "\u2713"),
140
+ " Removed ",
141
+ viberagDir,
142
+ "/")),
143
+ successfulRemovals.map(r => {
144
+ const editor = EDITORS.find(e => e.id === r.editor);
145
+ return (React.createElement(Text, { key: r.editor },
146
+ React.createElement(Text, { color: "green" }, "\u2713"),
147
+ " ",
148
+ editor?.name ?? r.editor,
149
+ React.createElement(Text, { dimColor: true },
150
+ " Updated ",
151
+ r.configPath)));
152
+ })),
153
+ globalScopeEditors.length > 0 && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
154
+ React.createElement(Text, { bold: true, color: "blue" }, "Manual cleanup needed:"),
155
+ globalScopeEditors.map(editor => (React.createElement(Text, { key: editor.id, dimColor: true },
156
+ "\u2022 ",
157
+ editor.name,
158
+ ": Remove \"viberag\" from ",
159
+ editor.configPath))))),
160
+ React.createElement(Box, { marginTop: 1 },
161
+ React.createElement(Text, { dimColor: true }, "Run /init to reinitialize VibeRAG.")),
162
+ React.createElement(Box, { marginTop: 1 },
163
+ React.createElement(SelectInput, { items: [{ label: 'Done', value: 'done' }], onSelect: () => onComplete() }))));
164
+ }
165
+ // Fallback
166
+ return (React.createElement(Box, { flexDirection: "column" },
167
+ React.createElement(Text, { color: "red" }, "Unknown wizard step")));
168
+ }
169
+ export default CleanWizard;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Multi-step initialization wizard component.
3
+ * Guides users through embedding provider selection and API key configuration.
4
+ */
5
+ import React from 'react';
6
+ import type { InitWizardConfig, EmbeddingProviderType } from '../../common/types.js';
7
+ type Props = {
8
+ step: number;
9
+ config: Partial<InitWizardConfig>;
10
+ isReinit: boolean;
11
+ /** Existing API key from previous config (for reinit flow) */
12
+ existingApiKey?: string;
13
+ /** Existing provider from previous config (for reinit flow) */
14
+ existingProvider?: EmbeddingProviderType;
15
+ onStepChange: (step: number, data?: Partial<InitWizardConfig>) => void;
16
+ onComplete: (config: InitWizardConfig) => void;
17
+ onCancel: () => void;
18
+ };
19
+ export declare function InitWizard({ step, config, isReinit, existingApiKey, existingProvider, onStepChange, onComplete, onCancel, }: Props): React.ReactElement;
20
+ export default InitWizard;
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Multi-step initialization wizard component.
3
+ * Guides users through embedding provider selection and API key configuration.
4
+ */
5
+ import React, { useState } from 'react';
6
+ import { Box, Text, useInput } from 'ink';
7
+ import SelectInput from 'ink-select-input';
8
+ /**
9
+ * Cloud providers that require API keys.
10
+ */
11
+ const CLOUD_PROVIDERS = ['gemini', 'mistral', 'openai'];
12
+ function isCloudProvider(provider) {
13
+ return CLOUD_PROVIDERS.includes(provider);
14
+ }
15
+ /**
16
+ * URLs to get API keys for each cloud provider.
17
+ */
18
+ const API_KEY_URLS = {
19
+ gemini: 'https://aistudio.google.com',
20
+ mistral: 'https://console.mistral.ai/api-keys/',
21
+ openai: 'https://platform.openai.com/api-keys',
22
+ };
23
+ /**
24
+ * Provider configurations with specs and pricing.
25
+ */
26
+ const PROVIDER_CONFIG = {
27
+ local: {
28
+ name: 'Local',
29
+ model: 'Qwen3-0.6B Q8',
30
+ modelFull: 'Qwen/Qwen3-Embedding-0.6B',
31
+ dims: '1024',
32
+ context: '32K',
33
+ cost: 'Free',
34
+ note: '~700MB download, ~1.2GB RAM',
35
+ description: 'Offline, no API key needed',
36
+ },
37
+ 'local-4b': {
38
+ name: 'Local 4B',
39
+ model: 'Qwen3-4B FP32',
40
+ modelFull: 'Qwen/Qwen3-Embedding-4B',
41
+ dims: '2560',
42
+ context: '32K',
43
+ cost: 'Free',
44
+ note: '~8GB download, ~8GB RAM',
45
+ description: 'Offline, better quality (+5 MTEB)',
46
+ },
47
+ gemini: {
48
+ name: 'Gemini',
49
+ model: 'gemini-embedding-001',
50
+ modelFull: 'gemini-embedding-001',
51
+ dims: '768',
52
+ context: '2K',
53
+ cost: 'Free tier',
54
+ note: 'API key required',
55
+ description: 'Fast API, free tier available',
56
+ },
57
+ mistral: {
58
+ name: 'Mistral',
59
+ model: 'codestral-embed',
60
+ modelFull: 'codestral-embed',
61
+ dims: '1024',
62
+ context: '8K',
63
+ cost: '$0.10/1M',
64
+ note: 'API key required',
65
+ description: 'Code-optimized embeddings',
66
+ },
67
+ openai: {
68
+ name: 'OpenAI',
69
+ model: 'text-embed-3-sm',
70
+ modelFull: 'text-embedding-3-small',
71
+ dims: '1536',
72
+ context: '8K',
73
+ cost: '$0.02/1M',
74
+ note: 'API key required',
75
+ description: 'Fast and reliable API',
76
+ },
77
+ };
78
+ // Simple provider options for selection
79
+ // Note: local-4b exists in code but hidden from UI - no transformers.js-compatible ONNX available yet
80
+ const PROVIDER_ITEMS = [
81
+ { label: 'Local - Qwen3-0.6B Q8 (~700MB, ~1.2GB RAM)', value: 'local' },
82
+ // {label: 'Local 4B - Qwen3-4B FP32 (~8GB, ~8GB RAM)', value: 'local-4b'}, // No ONNX available
83
+ { label: 'Gemini - gemini-embedding-001 (Free tier)', value: 'gemini' },
84
+ { label: 'Mistral - codestral-embed', value: 'mistral' },
85
+ { label: 'OpenAI - text-embedding-3-small', value: 'openai' },
86
+ ];
87
+ /**
88
+ * Local model info.
89
+ * Note: 4B not shown - no transformers.js-compatible ONNX available yet
90
+ */
91
+ const LOCAL_MODELS_DATA = [
92
+ { Model: 'Qwen3-0.6B', Quant: 'Q8', Download: '~700MB', RAM: '~1.2GB' },
93
+ ];
94
+ /**
95
+ * Frontier/API models - fastest, best quality.
96
+ */
97
+ const FRONTIER_MODELS_DATA = [
98
+ {
99
+ Provider: 'Gemini',
100
+ Model: 'gemini-embedding-001',
101
+ Dims: '768',
102
+ Cost: 'Free tier',
103
+ },
104
+ {
105
+ Provider: 'Mistral',
106
+ Model: 'codestral-embed',
107
+ Dims: '1024',
108
+ Cost: '$0.10/1M',
109
+ },
110
+ {
111
+ Provider: 'OpenAI',
112
+ Model: 'text-embed-3-small',
113
+ Dims: '1536',
114
+ Cost: '$0.02/1M',
115
+ },
116
+ ];
117
+ function SimpleTable({ data, columns, }) {
118
+ const dataRow = (row, isHeader = false) => {
119
+ const cells = columns.map(col => {
120
+ const value = row[col.key] ?? '';
121
+ return ' ' + value.padEnd(col.width - 1);
122
+ });
123
+ return (React.createElement(Text, { bold: isHeader, color: isHeader ? 'cyan' : undefined }, '│' + cells.join('│') + '│'));
124
+ };
125
+ // Header row from column keys
126
+ const headerRow = {};
127
+ for (const col of columns) {
128
+ headerRow[col.key] = col.key;
129
+ }
130
+ return (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
131
+ React.createElement(Text, null, '┌' + columns.map(col => '─'.repeat(col.width)).join('┬') + '┐'),
132
+ dataRow(headerRow, true),
133
+ React.createElement(Text, null, '├' + columns.map(col => '─'.repeat(col.width)).join('┼') + '┤'),
134
+ data.map((row, i) => (React.createElement(React.Fragment, { key: i }, dataRow(row)))),
135
+ React.createElement(Text, null, '└' + columns.map(col => '─'.repeat(col.width)).join('┴') + '┘')));
136
+ }
137
+ /**
138
+ * Comparison table component showing local and frontier models.
139
+ */
140
+ function ComparisonTable() {
141
+ const localColumns = [
142
+ { key: 'Model', width: 12 },
143
+ { key: 'Quant', width: 6 },
144
+ { key: 'Download', width: 10 },
145
+ { key: 'RAM', width: 8 },
146
+ ];
147
+ const frontierColumns = [
148
+ { key: 'Provider', width: 10 },
149
+ { key: 'Model', width: 20 },
150
+ { key: 'Dims', width: 6 },
151
+ { key: 'Cost', width: 11 },
152
+ ];
153
+ return (React.createElement(Box, { flexDirection: "column" },
154
+ React.createElement(Box, { marginTop: 1 },
155
+ React.createElement(Text, { bold: true, color: "yellow" }, "Local Models"),
156
+ React.createElement(Text, { dimColor: true }, " - Offline, Slower, Free")),
157
+ React.createElement(SimpleTable, { data: LOCAL_MODELS_DATA, columns: localColumns }),
158
+ React.createElement(Text, { dimColor: true, italic: true }, "* Initial indexing may take time. Future updates are incremental."),
159
+ React.createElement(Box, { marginTop: 1 },
160
+ React.createElement(Text, { bold: true, color: "green" }, "Frontier Models"),
161
+ React.createElement(Text, { dimColor: true }, " - Fastest, Best Quality")),
162
+ React.createElement(SimpleTable, { data: FRONTIER_MODELS_DATA, columns: frontierColumns })));
163
+ }
164
+ // Reinit confirmation options
165
+ const REINIT_ITEMS = [
166
+ { label: 'Continue (reinitialize)', value: 'continue' },
167
+ { label: 'Cancel', value: 'cancel' },
168
+ ];
169
+ // Final confirmation options
170
+ const CONFIRM_ITEMS = [
171
+ { label: 'Initialize', value: 'init' },
172
+ { label: 'Cancel', value: 'cancel' },
173
+ ];
174
+ // API key action options for reinit
175
+ const API_KEY_ACTION_ITEMS = [
176
+ { label: 'Keep existing API key', value: 'keep' },
177
+ { label: 'Enter new API key', value: 'new' },
178
+ ];
179
+ /**
180
+ * Simple text input component for API key entry.
181
+ */
182
+ function ApiKeyInputStep({ providerName, apiKeyInput, setApiKeyInput, onSubmit, }) {
183
+ // Handle text input (supports paste)
184
+ useInput((input, key) => {
185
+ if (key.return) {
186
+ onSubmit(apiKeyInput);
187
+ }
188
+ else if (key.backspace || key.delete) {
189
+ setApiKeyInput(apiKeyInput.slice(0, -1));
190
+ }
191
+ else if (!key.ctrl && !key.meta && input) {
192
+ // Add printable characters (supports multi-char paste)
193
+ setApiKeyInput(apiKeyInput + input);
194
+ }
195
+ });
196
+ return (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
197
+ React.createElement(Text, null,
198
+ "Enter your ",
199
+ providerName,
200
+ " API key:"),
201
+ React.createElement(Box, { marginTop: 1 },
202
+ React.createElement(Text, { color: "blue" }, "> "),
203
+ React.createElement(Text, null, apiKeyInput),
204
+ React.createElement(Text, { color: "gray" }, "\u2588")),
205
+ apiKeyInput.trim() === '' && (React.createElement(Text, { color: "yellow", dimColor: true }, "API key is required")),
206
+ React.createElement(Text, { dimColor: true }, "Press Enter to continue")));
207
+ }
208
+ export function InitWizard({ step, config, isReinit, existingApiKey, existingProvider, onStepChange, onComplete, onCancel, }) {
209
+ // State for API key input
210
+ const [apiKeyInput, setApiKeyInput] = useState('');
211
+ const [apiKeyAction, setApiKeyAction] = useState(null);
212
+ // Handle Escape to cancel
213
+ useInput((input, key) => {
214
+ if (key.escape || (key.ctrl && input === 'c')) {
215
+ onCancel();
216
+ }
217
+ });
218
+ // Normalize props with defensive defaults
219
+ const normalizedStep = typeof step === 'number' && !isNaN(step) ? step : 0;
220
+ const normalizedIsReinit = typeof isReinit === 'boolean' ? isReinit : false;
221
+ // Check if current provider is a cloud provider
222
+ const currentProvider = config.provider ?? 'local';
223
+ const needsApiKey = isCloudProvider(currentProvider);
224
+ // Check if we have an existing API key for the same provider
225
+ const hasExistingKeyForProvider = existingApiKey && existingProvider === currentProvider;
226
+ // Compute effective step (adjusted for non-reinit flow)
227
+ // Steps: 0=reinit confirm, 1=provider select, 2=api key (cloud only), 3=final confirm
228
+ const effectiveStep = normalizedIsReinit
229
+ ? normalizedStep
230
+ : normalizedStep + 1;
231
+ // Step 0 (reinit only): Confirmation
232
+ if (normalizedIsReinit && normalizedStep === 0) {
233
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
234
+ React.createElement(Text, { bold: true, color: "yellow" }, "Viberag is already initialized"),
235
+ React.createElement(Text, { dimColor: true }, "This will reset your configuration and reindex the codebase."),
236
+ React.createElement(Box, { marginTop: 1 },
237
+ React.createElement(SelectInput, { items: REINIT_ITEMS, onSelect: item => {
238
+ if (item.value === 'continue') {
239
+ onStepChange(1);
240
+ }
241
+ else {
242
+ onCancel();
243
+ }
244
+ } })),
245
+ React.createElement(Text, { dimColor: true },
246
+ '\n',
247
+ "Use arrow keys to navigate, Enter to select, Esc to cancel")));
248
+ }
249
+ // Step 1: Provider selection
250
+ // Fresh init: step=0 (effectiveStep=1), Reinit: step=1 (effectiveStep=1)
251
+ if (effectiveStep === 1) {
252
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
253
+ React.createElement(Text, { bold: true }, "Choose Embedding Provider"),
254
+ React.createElement(Box, { marginTop: 1 },
255
+ React.createElement(SelectInput, { items: PROVIDER_ITEMS, onSelect: item => {
256
+ // Reset API key state when provider changes
257
+ setApiKeyInput('');
258
+ setApiKeyAction(null);
259
+ // Use relative increment: step + 1
260
+ onStepChange(normalizedStep + 1, { provider: item.value });
261
+ } })),
262
+ React.createElement(ComparisonTable, null),
263
+ React.createElement(Box, { marginTop: 1 },
264
+ React.createElement(Text, { dimColor: true }, "\u2191/\u2193 navigate, Enter select, Esc cancel"))));
265
+ }
266
+ // Step 2: API Key input (cloud providers only)
267
+ // For local providers, skip to step 3 (confirmation)
268
+ if (effectiveStep === 2) {
269
+ // Skip API key step for local providers
270
+ if (!needsApiKey) {
271
+ // Auto-advance to confirmation
272
+ onStepChange(normalizedStep + 1);
273
+ return (React.createElement(Box, null,
274
+ React.createElement(Text, { dimColor: true }, "Loading...")));
275
+ }
276
+ const provider = currentProvider;
277
+ const info = PROVIDER_CONFIG[provider];
278
+ const apiKeyUrl = API_KEY_URLS[provider];
279
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
280
+ React.createElement(Text, { bold: true },
281
+ "Configure ",
282
+ info.name,
283
+ " API Key"),
284
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
285
+ React.createElement(Text, null,
286
+ "Get your API key:",
287
+ ' ',
288
+ React.createElement(Text, { color: "cyan", underline: true }, apiKeyUrl))),
289
+ hasExistingKeyForProvider && apiKeyAction === null ? (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
290
+ React.createElement(Text, { color: "green" },
291
+ "An API key is already configured for ",
292
+ info.name,
293
+ "."),
294
+ React.createElement(Box, { marginTop: 1 },
295
+ React.createElement(SelectInput, { items: API_KEY_ACTION_ITEMS, onSelect: item => {
296
+ if (item.value === 'keep') {
297
+ // Keep existing key, advance to confirmation
298
+ onStepChange(normalizedStep + 1, { apiKey: existingApiKey });
299
+ }
300
+ else {
301
+ // Show text input for new key
302
+ setApiKeyAction('new');
303
+ }
304
+ } })))) : (React.createElement(ApiKeyInputStep, { providerName: info.name, apiKeyInput: apiKeyInput, setApiKeyInput: setApiKeyInput, onSubmit: key => {
305
+ if (key.trim()) {
306
+ onStepChange(normalizedStep + 1, { apiKey: key.trim() });
307
+ }
308
+ } })),
309
+ React.createElement(Box, { marginTop: 1 },
310
+ React.createElement(Text, { dimColor: true }, "Esc to cancel"))));
311
+ }
312
+ // Step 3: Confirmation
313
+ if (effectiveStep === 3) {
314
+ const provider = config.provider ?? 'gemini';
315
+ const info = PROVIDER_CONFIG[provider];
316
+ const hasApiKey = !!config.apiKey;
317
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", paddingX: 2, paddingY: 1 },
318
+ React.createElement(Text, { bold: true }, "Ready to Initialize"),
319
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
320
+ React.createElement(Text, null,
321
+ React.createElement(Text, { dimColor: true }, "Provider: "),
322
+ React.createElement(Text, { bold: true }, info.name),
323
+ React.createElement(Text, { dimColor: true },
324
+ " - ",
325
+ info.description)),
326
+ React.createElement(Text, null,
327
+ React.createElement(Text, { dimColor: true }, "Model: "),
328
+ info.modelFull),
329
+ React.createElement(Text, null,
330
+ React.createElement(Text, { dimColor: true }, "Specs: "),
331
+ info.dims,
332
+ "d, ",
333
+ info.context,
334
+ " context"),
335
+ React.createElement(Text, null,
336
+ React.createElement(Text, { dimColor: true }, "Cost: "),
337
+ info.cost),
338
+ isCloudProvider(provider) && (React.createElement(Text, null,
339
+ React.createElement(Text, { dimColor: true }, "API Key: "),
340
+ hasApiKey ? (React.createElement(Text, { color: "green" }, "Configured")) : (React.createElement(Text, { color: "red" }, "Missing")))),
341
+ React.createElement(Text, null,
342
+ React.createElement(Text, { dimColor: true }, "Directory:"),
343
+ " .viberag/")),
344
+ React.createElement(Box, { marginTop: 1 },
345
+ React.createElement(SelectInput, { items: CONFIRM_ITEMS, onSelect: item => {
346
+ if (item.value === 'init') {
347
+ onComplete({ provider, apiKey: config.apiKey });
348
+ }
349
+ else {
350
+ onCancel();
351
+ }
352
+ } })),
353
+ React.createElement(Text, { dimColor: true }, "\u2191/\u2193 navigate, Enter select, Esc cancel")));
354
+ }
355
+ // Fallback (shouldn't happen)
356
+ return (React.createElement(Box, { flexDirection: "column" },
357
+ React.createElement(Text, { color: "red" }, "Unknown wizard step"),
358
+ React.createElement(Text, { dimColor: true },
359
+ "Debug: step=",
360
+ step,
361
+ " (",
362
+ typeof step,
363
+ "), isReinit=",
364
+ String(isReinit),
365
+ " (",
366
+ typeof isReinit,
367
+ "), effectiveStep=",
368
+ effectiveStep)));
369
+ }
370
+ export default InitWizard;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * MCP Setup Wizard Component
3
+ *
4
+ * Multi-step wizard for configuring VibeRAG's MCP server
5
+ * across various AI coding tools.
6
+ */
7
+ import React from 'react';
8
+ import { type EditorId } from '../data/mcp-editors.js';
9
+ import { type McpSetupResult } from '../commands/mcp-setup.js';
10
+ /**
11
+ * Wizard step types.
12
+ */
13
+ export type McpSetupStep = 'prompt' | 'select' | 'configure' | 'summary';
14
+ /**
15
+ * Wizard configuration.
16
+ */
17
+ export interface McpSetupWizardConfig {
18
+ selectedEditors: EditorId[];
19
+ results: McpSetupResult[];
20
+ }
21
+ type Props = {
22
+ step: McpSetupStep;
23
+ config: Partial<McpSetupWizardConfig>;
24
+ projectRoot: string;
25
+ /** Whether this is shown after /init (shows prompt step) */
26
+ showPrompt: boolean;
27
+ onStepChange: (step: McpSetupStep, data?: Partial<McpSetupWizardConfig>) => void;
28
+ onComplete: (config: McpSetupWizardConfig) => void;
29
+ onCancel: () => void;
30
+ /** For outputting instructions to console */
31
+ addOutput?: (type: 'system' | 'user', content: string) => void;
32
+ };
33
+ /**
34
+ * MCP Setup Wizard main component.
35
+ */
36
+ export declare function McpSetupWizard({ step, config, projectRoot, showPrompt, onStepChange, onComplete, onCancel, addOutput, }: Props): React.ReactElement;
37
+ export default McpSetupWizard;