@wundr.io/cli 1.0.0 → 1.0.2-dev.20260530174250.ef0ec927

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 (230) hide show
  1. package/README.md +696 -280
  2. package/bin/wundr.js +13 -5
  3. package/package.json +30 -9
  4. package/src/ai/ai-service.ts +6 -4
  5. package/src/ai/claude-client.ts +6 -2
  6. package/src/ai/conversation-manager.ts +12 -5
  7. package/src/cli.ts +42 -13
  8. package/src/commands/ai.ts +340 -64
  9. package/src/commands/alignment.ts +1212 -0
  10. package/src/commands/analyze-optimized.ts +371 -33
  11. package/src/commands/analyze.ts +8 -6
  12. package/src/commands/batch.ts +166 -26
  13. package/src/commands/chat.ts +20 -10
  14. package/src/commands/claude-init.ts +31 -27
  15. package/src/commands/claude-setup.ts +761 -81
  16. package/src/commands/computer-setup.ts +524 -12
  17. package/src/commands/create-command.ts +3 -3
  18. package/src/commands/create.ts +9 -6
  19. package/src/commands/dashboard.ts +11 -6
  20. package/src/commands/govern.ts +11 -6
  21. package/src/commands/governance.ts +1005 -0
  22. package/src/commands/guardian.ts +887 -0
  23. package/src/commands/init.ts +104 -11
  24. package/src/commands/orchestrator.ts +789 -0
  25. package/src/commands/performance-optimizer.ts +15 -10
  26. package/src/commands/plugins.ts +8 -5
  27. package/src/commands/project-update.ts +1156 -0
  28. package/src/commands/rag.ts +1011 -0
  29. package/src/commands/session.ts +631 -0
  30. package/src/commands/setup.ts +42 -344
  31. package/src/commands/test-init.ts +3 -2
  32. package/src/commands/test.ts +3 -2
  33. package/src/commands/watch.ts +21 -11
  34. package/src/commands/worktree.ts +1057 -0
  35. package/src/context/context-manager.ts +5 -2
  36. package/src/context/session-manager.ts +18 -7
  37. package/src/framework/command-interface.ts +520 -0
  38. package/src/framework/command-registry.ts +942 -0
  39. package/src/framework/completion-exporter.ts +383 -0
  40. package/src/framework/debug-logger.ts +519 -0
  41. package/src/framework/error-handler.ts +867 -0
  42. package/src/framework/help-generator.ts +540 -0
  43. package/src/framework/index.ts +169 -0
  44. package/src/framework/interactive-repl.ts +703 -0
  45. package/src/framework/output-formatter.ts +834 -0
  46. package/src/framework/progress-manager.ts +539 -0
  47. package/src/index.ts +3 -2
  48. package/src/interactive/interactive-mode.ts +14 -7
  49. package/src/lib/conflict-resolution.ts +818 -0
  50. package/src/lib/merge-strategy.ts +550 -0
  51. package/src/lib/safety-mechanisms.ts +451 -0
  52. package/src/lib/state-detection.ts +1030 -0
  53. package/src/nlp/command-mapper.ts +8 -3
  54. package/src/nlp/command-parser.ts +5 -2
  55. package/src/nlp/intent-parser.ts +23 -9
  56. package/src/plugins/plugin-manager.ts +50 -24
  57. package/src/tests/computer-setup-integration.test.ts +470 -0
  58. package/src/types/index.ts +1 -1
  59. package/src/types/modules.d.ts +425 -1
  60. package/src/utils/backup-rollback-manager.ts +366 -0
  61. package/src/utils/claude-config-installer.ts +823 -0
  62. package/src/utils/config-manager.ts +9 -6
  63. package/src/utils/error-handler.ts +3 -1
  64. package/src/utils/logger.ts +35 -12
  65. package/templates/batch/ci-cd.yaml +7 -7
  66. package/test-suites/api/health.spec.ts +20 -23
  67. package/test-suites/helpers/test-config.ts +14 -13
  68. package/test-suites/ui/accessibility.spec.ts +27 -22
  69. package/test-suites/ui/smoke.spec.ts +26 -21
  70. package/dist/ai/ai-service.d.ts +0 -152
  71. package/dist/ai/ai-service.d.ts.map +0 -1
  72. package/dist/ai/ai-service.js +0 -430
  73. package/dist/ai/ai-service.js.map +0 -1
  74. package/dist/ai/claude-client.d.ts +0 -130
  75. package/dist/ai/claude-client.d.ts.map +0 -1
  76. package/dist/ai/claude-client.js +0 -339
  77. package/dist/ai/claude-client.js.map +0 -1
  78. package/dist/ai/conversation-manager.d.ts +0 -164
  79. package/dist/ai/conversation-manager.d.ts.map +0 -1
  80. package/dist/ai/conversation-manager.js +0 -612
  81. package/dist/ai/conversation-manager.js.map +0 -1
  82. package/dist/ai/index.d.ts +0 -5
  83. package/dist/ai/index.d.ts.map +0 -1
  84. package/dist/ai/index.js +0 -8
  85. package/dist/ai/index.js.map +0 -1
  86. package/dist/cli.d.ts +0 -36
  87. package/dist/cli.d.ts.map +0 -1
  88. package/dist/cli.js +0 -173
  89. package/dist/cli.js.map +0 -1
  90. package/dist/commands/ai.d.ts +0 -89
  91. package/dist/commands/ai.d.ts.map +0 -1
  92. package/dist/commands/ai.js +0 -735
  93. package/dist/commands/ai.js.map +0 -1
  94. package/dist/commands/analyze-optimized.d.ts +0 -14
  95. package/dist/commands/analyze-optimized.d.ts.map +0 -1
  96. package/dist/commands/analyze-optimized.js +0 -437
  97. package/dist/commands/analyze-optimized.js.map +0 -1
  98. package/dist/commands/analyze.d.ts +0 -65
  99. package/dist/commands/analyze.d.ts.map +0 -1
  100. package/dist/commands/analyze.js +0 -435
  101. package/dist/commands/analyze.js.map +0 -1
  102. package/dist/commands/batch.d.ts +0 -71
  103. package/dist/commands/batch.d.ts.map +0 -1
  104. package/dist/commands/batch.js +0 -738
  105. package/dist/commands/batch.js.map +0 -1
  106. package/dist/commands/chat.d.ts +0 -71
  107. package/dist/commands/chat.d.ts.map +0 -1
  108. package/dist/commands/chat.js +0 -674
  109. package/dist/commands/chat.js.map +0 -1
  110. package/dist/commands/claude-init.d.ts +0 -28
  111. package/dist/commands/claude-init.d.ts.map +0 -1
  112. package/dist/commands/claude-init.js +0 -587
  113. package/dist/commands/claude-init.js.map +0 -1
  114. package/dist/commands/claude-setup.d.ts +0 -32
  115. package/dist/commands/claude-setup.d.ts.map +0 -1
  116. package/dist/commands/claude-setup.js +0 -570
  117. package/dist/commands/claude-setup.js.map +0 -1
  118. package/dist/commands/computer-setup-commands.d.ts +0 -39
  119. package/dist/commands/computer-setup-commands.d.ts.map +0 -1
  120. package/dist/commands/computer-setup-commands.js +0 -563
  121. package/dist/commands/computer-setup-commands.js.map +0 -1
  122. package/dist/commands/computer-setup.d.ts +0 -7
  123. package/dist/commands/computer-setup.d.ts.map +0 -1
  124. package/dist/commands/computer-setup.js +0 -481
  125. package/dist/commands/computer-setup.js.map +0 -1
  126. package/dist/commands/create-command.d.ts +0 -7
  127. package/dist/commands/create-command.d.ts.map +0 -1
  128. package/dist/commands/create-command.js +0 -158
  129. package/dist/commands/create-command.js.map +0 -1
  130. package/dist/commands/create.d.ts +0 -74
  131. package/dist/commands/create.d.ts.map +0 -1
  132. package/dist/commands/create.js +0 -556
  133. package/dist/commands/create.js.map +0 -1
  134. package/dist/commands/dashboard.d.ts +0 -91
  135. package/dist/commands/dashboard.d.ts.map +0 -1
  136. package/dist/commands/dashboard.js +0 -537
  137. package/dist/commands/dashboard.js.map +0 -1
  138. package/dist/commands/govern.d.ts +0 -70
  139. package/dist/commands/govern.d.ts.map +0 -1
  140. package/dist/commands/govern.js +0 -480
  141. package/dist/commands/govern.js.map +0 -1
  142. package/dist/commands/init.d.ts +0 -55
  143. package/dist/commands/init.d.ts.map +0 -1
  144. package/dist/commands/init.js +0 -584
  145. package/dist/commands/init.js.map +0 -1
  146. package/dist/commands/performance-optimizer.d.ts +0 -30
  147. package/dist/commands/performance-optimizer.d.ts.map +0 -1
  148. package/dist/commands/performance-optimizer.js +0 -649
  149. package/dist/commands/performance-optimizer.js.map +0 -1
  150. package/dist/commands/plugins.d.ts +0 -87
  151. package/dist/commands/plugins.d.ts.map +0 -1
  152. package/dist/commands/plugins.js +0 -685
  153. package/dist/commands/plugins.js.map +0 -1
  154. package/dist/commands/setup.d.ts +0 -29
  155. package/dist/commands/setup.d.ts.map +0 -1
  156. package/dist/commands/setup.js +0 -399
  157. package/dist/commands/setup.js.map +0 -1
  158. package/dist/commands/test-init.d.ts +0 -9
  159. package/dist/commands/test-init.d.ts.map +0 -1
  160. package/dist/commands/test-init.js +0 -222
  161. package/dist/commands/test-init.js.map +0 -1
  162. package/dist/commands/test.d.ts +0 -25
  163. package/dist/commands/test.d.ts.map +0 -1
  164. package/dist/commands/test.js +0 -217
  165. package/dist/commands/test.js.map +0 -1
  166. package/dist/commands/watch.d.ts +0 -76
  167. package/dist/commands/watch.d.ts.map +0 -1
  168. package/dist/commands/watch.js +0 -610
  169. package/dist/commands/watch.js.map +0 -1
  170. package/dist/context/context-manager.d.ts +0 -155
  171. package/dist/context/context-manager.d.ts.map +0 -1
  172. package/dist/context/context-manager.js +0 -383
  173. package/dist/context/context-manager.js.map +0 -1
  174. package/dist/context/index.d.ts +0 -3
  175. package/dist/context/index.d.ts.map +0 -1
  176. package/dist/context/index.js +0 -6
  177. package/dist/context/index.js.map +0 -1
  178. package/dist/context/session-manager.d.ts +0 -207
  179. package/dist/context/session-manager.d.ts.map +0 -1
  180. package/dist/context/session-manager.js +0 -682
  181. package/dist/context/session-manager.js.map +0 -1
  182. package/dist/index.d.ts +0 -8
  183. package/dist/index.d.ts.map +0 -1
  184. package/dist/index.js +0 -51
  185. package/dist/index.js.map +0 -1
  186. package/dist/interactive/interactive-mode.d.ts +0 -76
  187. package/dist/interactive/interactive-mode.d.ts.map +0 -1
  188. package/dist/interactive/interactive-mode.js +0 -730
  189. package/dist/interactive/interactive-mode.js.map +0 -1
  190. package/dist/nlp/command-mapper.d.ts +0 -174
  191. package/dist/nlp/command-mapper.d.ts.map +0 -1
  192. package/dist/nlp/command-mapper.js +0 -623
  193. package/dist/nlp/command-mapper.js.map +0 -1
  194. package/dist/nlp/command-parser.d.ts +0 -106
  195. package/dist/nlp/command-parser.d.ts.map +0 -1
  196. package/dist/nlp/command-parser.js +0 -416
  197. package/dist/nlp/command-parser.js.map +0 -1
  198. package/dist/nlp/index.d.ts +0 -5
  199. package/dist/nlp/index.d.ts.map +0 -1
  200. package/dist/nlp/index.js +0 -8
  201. package/dist/nlp/index.js.map +0 -1
  202. package/dist/nlp/intent-classifier.d.ts +0 -59
  203. package/dist/nlp/intent-classifier.d.ts.map +0 -1
  204. package/dist/nlp/intent-classifier.js +0 -384
  205. package/dist/nlp/intent-classifier.js.map +0 -1
  206. package/dist/nlp/intent-parser.d.ts +0 -152
  207. package/dist/nlp/intent-parser.d.ts.map +0 -1
  208. package/dist/nlp/intent-parser.js +0 -739
  209. package/dist/nlp/intent-parser.js.map +0 -1
  210. package/dist/plugins/plugin-manager.d.ts +0 -120
  211. package/dist/plugins/plugin-manager.d.ts.map +0 -1
  212. package/dist/plugins/plugin-manager.js +0 -595
  213. package/dist/plugins/plugin-manager.js.map +0 -1
  214. package/dist/types/index.d.ts +0 -224
  215. package/dist/types/index.d.ts.map +0 -1
  216. package/dist/types/index.js +0 -3
  217. package/dist/types/index.js.map +0 -1
  218. package/dist/utils/config-manager.d.ts +0 -73
  219. package/dist/utils/config-manager.d.ts.map +0 -1
  220. package/dist/utils/config-manager.js +0 -339
  221. package/dist/utils/config-manager.js.map +0 -1
  222. package/dist/utils/error-handler.d.ts +0 -46
  223. package/dist/utils/error-handler.d.ts.map +0 -1
  224. package/dist/utils/error-handler.js +0 -169
  225. package/dist/utils/error-handler.js.map +0 -1
  226. package/dist/utils/logger.d.ts +0 -25
  227. package/dist/utils/logger.d.ts.map +0 -1
  228. package/dist/utils/logger.js +0 -94
  229. package/dist/utils/logger.js.map +0 -1
  230. package/src/commands/computer-setup-commands.ts +0 -709
@@ -0,0 +1,1011 @@
1
+ /**
2
+ * RAG (Retrieval-Augmented Generation) CLI Commands
3
+ * Manages RAG stores for AI-powered code understanding and retrieval
4
+ */
5
+
6
+ import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
7
+ import * as fs from 'fs/promises';
8
+ import * as os from 'os';
9
+ import * as path from 'path';
10
+
11
+ import chalk from 'chalk';
12
+ import { Command } from 'commander';
13
+ import inquirer from 'inquirer';
14
+ import ora from 'ora';
15
+
16
+ // Constants
17
+ const RAG_BASE_DIR = path.join(os.homedir(), '.wundr', 'rag-stores');
18
+ const RAG_GLOBAL_DIR = path.join(RAG_BASE_DIR, 'global');
19
+ const RAG_PROJECT_DIR = path.join(RAG_BASE_DIR, 'project-specific');
20
+ const CONFIG_FILE = path.join(RAG_BASE_DIR, 'config.json');
21
+
22
+ // Types
23
+ interface RAGConfig {
24
+ version: string;
25
+ stores: {
26
+ global: StoreConfig;
27
+ 'project-specific': StoreConfig;
28
+ };
29
+ embeddings: {
30
+ model: string;
31
+ dimensions: number;
32
+ batchSize: number;
33
+ };
34
+ indexing: {
35
+ chunkSize: number;
36
+ chunkOverlap: number;
37
+ maxTokens: number;
38
+ };
39
+ retrieval: {
40
+ topK: number;
41
+ minScore: number;
42
+ };
43
+ }
44
+
45
+ interface StoreConfig {
46
+ path: string;
47
+ description: string;
48
+ autoSync: boolean;
49
+ pruneDeleted: boolean;
50
+ }
51
+
52
+ interface StoreMetadata {
53
+ name: string;
54
+ path: string;
55
+ embeddingCount: number;
56
+ lastSync: string | null;
57
+ lastPrune: string | null;
58
+ lastIndex: string | null;
59
+ status: 'ready' | 'syncing' | 'error' | 'unknown';
60
+ sourceDir?: string;
61
+ }
62
+
63
+ // Utility functions
64
+ function getTimestamp(): string {
65
+ return new Date().toISOString();
66
+ }
67
+
68
+ function formatBytes(bytes: number): string {
69
+ if (bytes === 0) {
70
+ return '0 Bytes';
71
+ }
72
+ const k = 1024;
73
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
74
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
75
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
76
+ }
77
+
78
+ function getDirSize(dirPath: string): number {
79
+ if (!existsSync(dirPath)) {
80
+ return 0;
81
+ }
82
+
83
+ let totalSize = 0;
84
+ try {
85
+ const files = readdirSync(dirPath);
86
+
87
+ for (const file of files) {
88
+ const filePath = path.join(dirPath, file);
89
+ const stat = statSync(filePath);
90
+
91
+ if (stat.isDirectory()) {
92
+ totalSize += getDirSize(filePath);
93
+ } else {
94
+ totalSize += stat.size;
95
+ }
96
+ }
97
+ } catch {
98
+ // Ignore permission errors
99
+ }
100
+
101
+ return totalSize;
102
+ }
103
+
104
+ async function loadConfig(): Promise<RAGConfig> {
105
+ try {
106
+ const configContent = await fs.readFile(CONFIG_FILE, 'utf-8');
107
+ return JSON.parse(configContent) as RAGConfig;
108
+ } catch {
109
+ return getDefaultConfig();
110
+ }
111
+ }
112
+
113
+ function getDefaultConfig(): RAGConfig {
114
+ return {
115
+ version: '1.0.0',
116
+ stores: {
117
+ global: {
118
+ path: RAG_GLOBAL_DIR,
119
+ description: 'Global RAG store for shared knowledge',
120
+ autoSync: true,
121
+ pruneDeleted: true,
122
+ },
123
+ 'project-specific': {
124
+ path: RAG_PROJECT_DIR,
125
+ description: 'Project-specific RAG stores',
126
+ autoSync: false,
127
+ pruneDeleted: false,
128
+ },
129
+ },
130
+ embeddings: {
131
+ model: 'text-embedding-004',
132
+ dimensions: 768,
133
+ batchSize: 100,
134
+ },
135
+ indexing: {
136
+ chunkSize: 1000,
137
+ chunkOverlap: 200,
138
+ maxTokens: 8192,
139
+ },
140
+ retrieval: {
141
+ topK: 5,
142
+ minScore: 0.7,
143
+ },
144
+ };
145
+ }
146
+
147
+ async function saveConfig(config: RAGConfig): Promise<void> {
148
+ await fs.mkdir(path.dirname(CONFIG_FILE), { recursive: true });
149
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
150
+ }
151
+
152
+ async function getAllStores(): Promise<string[]> {
153
+ const stores: string[] = [];
154
+
155
+ if (existsSync(RAG_GLOBAL_DIR)) {
156
+ stores.push(RAG_GLOBAL_DIR);
157
+ }
158
+
159
+ if (existsSync(RAG_PROJECT_DIR)) {
160
+ try {
161
+ const projectStores = readdirSync(RAG_PROJECT_DIR);
162
+ for (const store of projectStores) {
163
+ const storePath = path.join(RAG_PROJECT_DIR, store);
164
+ if (statSync(storePath).isDirectory()) {
165
+ stores.push(storePath);
166
+ }
167
+ }
168
+ } catch {
169
+ // Ignore permission errors
170
+ }
171
+ }
172
+
173
+ return stores;
174
+ }
175
+
176
+ async function getStoreMetadata(storePath: string): Promise<StoreMetadata> {
177
+ const name = path.basename(storePath);
178
+ const embeddingsDir = path.join(storePath, 'embeddings');
179
+ const syncMetadataPath = path.join(storePath, 'metadata', 'sync.json');
180
+ const pruneMetadataPath = path.join(storePath, 'metadata', 'prune.json');
181
+ const indexMetadataPath = path.join(storePath, 'metadata', 'index.json');
182
+
183
+ let embeddingCount = 0;
184
+ if (existsSync(embeddingsDir)) {
185
+ try {
186
+ const files = readdirSync(embeddingsDir).filter(f => f.endsWith('.json'));
187
+ embeddingCount = files.length;
188
+ } catch {
189
+ // Ignore permission errors
190
+ }
191
+ }
192
+
193
+ let lastSync: string | null = null;
194
+ let sourceDir: string | undefined;
195
+ let status: StoreMetadata['status'] = 'unknown';
196
+
197
+ if (existsSync(syncMetadataPath)) {
198
+ try {
199
+ const syncData = JSON.parse(await fs.readFile(syncMetadataPath, 'utf-8'));
200
+ lastSync = syncData.lastSync || null;
201
+ sourceDir = syncData.sourceDir;
202
+ status = syncData.status === 'synced' ? 'ready' : syncData.status;
203
+ } catch {
204
+ // Ignore parse errors
205
+ }
206
+ }
207
+
208
+ let lastPrune: string | null = null;
209
+ if (existsSync(pruneMetadataPath)) {
210
+ try {
211
+ const pruneData = JSON.parse(
212
+ await fs.readFile(pruneMetadataPath, 'utf-8')
213
+ );
214
+ lastPrune = pruneData.lastPrune || null;
215
+ } catch {
216
+ // Ignore parse errors
217
+ }
218
+ }
219
+
220
+ let lastIndex: string | null = null;
221
+ if (existsSync(indexMetadataPath)) {
222
+ try {
223
+ const indexData = JSON.parse(
224
+ await fs.readFile(indexMetadataPath, 'utf-8')
225
+ );
226
+ lastIndex = indexData.lastIndex || null;
227
+ } catch {
228
+ // Ignore parse errors
229
+ }
230
+ }
231
+
232
+ return {
233
+ name,
234
+ path: storePath,
235
+ embeddingCount,
236
+ lastSync,
237
+ lastPrune,
238
+ lastIndex,
239
+ status,
240
+ sourceDir,
241
+ };
242
+ }
243
+
244
+ // Create RAG command
245
+ export function createRAGCommand(): Command {
246
+ const command = new Command('rag')
247
+ .description(
248
+ 'Manage RAG (Retrieval-Augmented Generation) stores for AI-powered code understanding'
249
+ )
250
+ .addHelpText(
251
+ 'after',
252
+ chalk.gray(`
253
+ Examples:
254
+ ${chalk.green('wundr rag status')} Show RAG store status
255
+ ${chalk.green('wundr rag sync')} Sync all RAG stores
256
+ ${chalk.green('wundr rag prune')} Remove deleted files from stores
257
+ ${chalk.green('wundr rag reindex')} Re-index stores
258
+ ${chalk.green('wundr rag create myproject')} Create a new project-specific store
259
+ ${chalk.green('wundr rag setup')} Run initial RAG infrastructure setup
260
+ `)
261
+ );
262
+
263
+ // Status command (default)
264
+ command
265
+ .command('status', { isDefault: true })
266
+ .description('Show RAG store status and statistics')
267
+ .option('--json', 'Output as JSON')
268
+ .action(async options => {
269
+ await showStatus(options);
270
+ });
271
+
272
+ // Sync command
273
+ command
274
+ .command('sync')
275
+ .description('Sync all RAG stores with their source directories')
276
+ .option('--store <name>', 'Only sync specific store')
277
+ .option('--dry-run', 'Show what would be synced without making changes')
278
+ .action(async options => {
279
+ await syncStores(options);
280
+ });
281
+
282
+ // Prune command
283
+ command
284
+ .command('prune')
285
+ .description('Remove deleted files from RAG stores')
286
+ .option('--store <name>', 'Only prune specific store')
287
+ .option('--dry-run', 'Show what would be pruned without making changes')
288
+ .action(async options => {
289
+ await pruneStores(options);
290
+ });
291
+
292
+ // Reindex command
293
+ command
294
+ .command('reindex')
295
+ .description('Re-index RAG stores with updated configurations')
296
+ .option('--store <name>', 'Only reindex specific store')
297
+ .option('--force', 'Force complete reindex')
298
+ .action(async options => {
299
+ await reindexStores(options);
300
+ });
301
+
302
+ // Create command
303
+ command
304
+ .command('create')
305
+ .description('Create a new RAG store')
306
+ .argument('<name>', 'Store name')
307
+ .option('-s, --source <path>', 'Source directory to index')
308
+ .option('-g, --global', 'Create as global store')
309
+ .action(async (name, options) => {
310
+ await createStore(name, options);
311
+ });
312
+
313
+ // Setup command
314
+ command
315
+ .command('setup')
316
+ .description('Run initial RAG infrastructure setup')
317
+ .option('--skip-api-key', 'Skip GEMINI_API_KEY configuration')
318
+ .action(async options => {
319
+ await runSetup(options);
320
+ });
321
+
322
+ // Config command
323
+ command
324
+ .command('config')
325
+ .description('View or modify RAG configuration')
326
+ .option('--get <key>', 'Get configuration value')
327
+ .option('--set <key=value>', 'Set configuration value')
328
+ .option('--reset', 'Reset to default configuration')
329
+ .action(async options => {
330
+ await manageConfig(options);
331
+ });
332
+
333
+ // Delete command
334
+ command
335
+ .command('delete')
336
+ .description('Delete a RAG store')
337
+ .argument('<name>', 'Store name')
338
+ .option('--force', 'Skip confirmation')
339
+ .action(async (name, options) => {
340
+ await deleteStore(name, options);
341
+ });
342
+
343
+ return command;
344
+ }
345
+
346
+ // Command implementations
347
+ async function showStatus(options: { json?: boolean }): Promise<void> {
348
+ const spinner = ora('Loading RAG store status...').start();
349
+
350
+ try {
351
+ const stores = await getAllStores();
352
+ const storeMetadata: StoreMetadata[] = [];
353
+ let totalEmbeddings = 0;
354
+
355
+ for (const store of stores) {
356
+ const metadata = await getStoreMetadata(store);
357
+ storeMetadata.push(metadata);
358
+ totalEmbeddings += metadata.embeddingCount;
359
+ }
360
+
361
+ const totalSize = getDirSize(RAG_BASE_DIR);
362
+
363
+ spinner.stop();
364
+
365
+ if (options.json) {
366
+ console.log(
367
+ JSON.stringify(
368
+ {
369
+ timestamp: getTimestamp(),
370
+ stores: storeMetadata,
371
+ totalEmbeddings,
372
+ totalDiskUsage: formatBytes(totalSize),
373
+ configPath: CONFIG_FILE,
374
+ },
375
+ null,
376
+ 2
377
+ )
378
+ );
379
+ return;
380
+ }
381
+
382
+ console.log(chalk.cyan('\nRAG Store Status Report'));
383
+ console.log(chalk.gray('='.repeat(70)));
384
+ console.log(chalk.white('Config:'), chalk.gray(CONFIG_FILE));
385
+ console.log(chalk.white('Total Embeddings:'), chalk.green(totalEmbeddings));
386
+ console.log(
387
+ chalk.white('Disk Usage:'),
388
+ chalk.green(formatBytes(totalSize))
389
+ );
390
+ console.log(chalk.gray('-'.repeat(70)));
391
+
392
+ if (storeMetadata.length === 0) {
393
+ console.log(
394
+ chalk.yellow(
395
+ '\nNo RAG stores found. Run "wundr rag setup" to get started.\n'
396
+ )
397
+ );
398
+ return;
399
+ }
400
+
401
+ console.log(
402
+ chalk.cyan(
403
+ padRight('Store', 25) +
404
+ padRight('Embeddings', 12) +
405
+ padRight('Last Sync', 18) +
406
+ padRight('Status', 15)
407
+ )
408
+ );
409
+ console.log(chalk.gray('-'.repeat(70)));
410
+
411
+ for (const store of storeMetadata) {
412
+ const lastSync = store.lastSync
413
+ ? new Date(store.lastSync).toLocaleDateString()
414
+ : 'Never';
415
+
416
+ let statusColor = chalk.yellow;
417
+ if (store.status === 'ready') {
418
+ statusColor = chalk.green;
419
+ }
420
+ if (store.status === 'error') {
421
+ statusColor = chalk.red;
422
+ }
423
+
424
+ console.log(
425
+ padRight(store.name, 25) +
426
+ padRight(String(store.embeddingCount), 12) +
427
+ padRight(lastSync, 18) +
428
+ statusColor(padRight(store.status, 15))
429
+ );
430
+ }
431
+
432
+ console.log(chalk.gray('-'.repeat(70)));
433
+ console.log('');
434
+ } catch (error) {
435
+ spinner.fail('Failed to load RAG status');
436
+ console.error(
437
+ chalk.red(error instanceof Error ? error.message : String(error))
438
+ );
439
+ }
440
+ }
441
+
442
+ async function syncStores(options: {
443
+ store?: string;
444
+ dryRun?: boolean;
445
+ }): Promise<void> {
446
+ const spinner = ora('Syncing RAG stores...').start();
447
+
448
+ try {
449
+ const stores = await getAllStores();
450
+ let synced = 0;
451
+ let skipped = 0;
452
+
453
+ spinner.stop();
454
+ console.log(chalk.cyan('\nSyncing RAG Stores\n'));
455
+
456
+ for (const storePath of stores) {
457
+ const storeName = path.basename(storePath);
458
+
459
+ if (options.store && storeName !== options.store) {
460
+ continue;
461
+ }
462
+
463
+ const sourceMetadataPath = path.join(
464
+ storePath,
465
+ 'metadata',
466
+ 'source.json'
467
+ );
468
+
469
+ if (!existsSync(sourceMetadataPath)) {
470
+ console.log(chalk.yellow(` [SKIP] ${storeName}: No source metadata`));
471
+ skipped++;
472
+ continue;
473
+ }
474
+
475
+ try {
476
+ const sourceMetadata = JSON.parse(
477
+ await fs.readFile(sourceMetadataPath, 'utf-8')
478
+ );
479
+ const sourceDir = sourceMetadata.sourceDir;
480
+
481
+ if (!sourceDir || !existsSync(sourceDir)) {
482
+ console.log(
483
+ chalk.yellow(` [SKIP] ${storeName}: Source directory not found`)
484
+ );
485
+ skipped++;
486
+ continue;
487
+ }
488
+
489
+ // Count files
490
+ let fileCount = 0;
491
+ const countFiles = (dir: string): void => {
492
+ if (!existsSync(dir)) {
493
+ return;
494
+ }
495
+ try {
496
+ const entries = readdirSync(dir);
497
+ for (const entry of entries) {
498
+ const fullPath = path.join(dir, entry);
499
+ const stat = statSync(fullPath);
500
+ if (
501
+ stat.isDirectory() &&
502
+ !entry.startsWith('.') &&
503
+ entry !== 'node_modules'
504
+ ) {
505
+ countFiles(fullPath);
506
+ } else if (
507
+ stat.isFile() &&
508
+ /\.(ts|js|tsx|jsx|md|json)$/.test(entry)
509
+ ) {
510
+ fileCount++;
511
+ }
512
+ }
513
+ } catch {
514
+ // Ignore permission errors
515
+ }
516
+ };
517
+ countFiles(sourceDir);
518
+
519
+ if (options.dryRun) {
520
+ console.log(
521
+ chalk.blue(
522
+ ` [DRY-RUN] ${storeName}: Would sync ${fileCount} files`
523
+ )
524
+ );
525
+ } else {
526
+ // Update sync metadata
527
+ const syncMetadata = {
528
+ lastSync: getTimestamp(),
529
+ filesCount: fileCount,
530
+ sourceDir,
531
+ status: 'synced',
532
+ };
533
+
534
+ await fs.mkdir(path.join(storePath, 'metadata'), { recursive: true });
535
+ await fs.writeFile(
536
+ path.join(storePath, 'metadata', 'sync.json'),
537
+ JSON.stringify(syncMetadata, null, 2)
538
+ );
539
+
540
+ console.log(
541
+ chalk.green(` [OK] ${storeName}: Synced ${fileCount} files`)
542
+ );
543
+ }
544
+ synced++;
545
+ } catch (error) {
546
+ console.log(
547
+ chalk.red(
548
+ ` [ERROR] ${storeName}: ${error instanceof Error ? error.message : String(error)}`
549
+ )
550
+ );
551
+ }
552
+ }
553
+
554
+ console.log(chalk.gray('\n' + '-'.repeat(50)));
555
+ console.log(
556
+ chalk.green(`Sync complete: ${synced} synced, ${skipped} skipped\n`)
557
+ );
558
+ } catch (error) {
559
+ spinner.fail('Failed to sync stores');
560
+ console.error(
561
+ chalk.red(error instanceof Error ? error.message : String(error))
562
+ );
563
+ }
564
+ }
565
+
566
+ async function pruneStores(options: {
567
+ store?: string;
568
+ dryRun?: boolean;
569
+ }): Promise<void> {
570
+ const spinner = ora('Pruning RAG stores...').start();
571
+
572
+ try {
573
+ const stores = await getAllStores();
574
+ let totalPruned = 0;
575
+
576
+ spinner.stop();
577
+ console.log(chalk.cyan('\nPruning RAG Stores\n'));
578
+
579
+ for (const storePath of stores) {
580
+ const storeName = path.basename(storePath);
581
+
582
+ if (options.store && storeName !== options.store) {
583
+ continue;
584
+ }
585
+
586
+ const embeddingsDir = path.join(storePath, 'embeddings');
587
+ let pruned = 0;
588
+
589
+ if (existsSync(embeddingsDir)) {
590
+ try {
591
+ const embeddingFiles = readdirSync(embeddingsDir).filter(f =>
592
+ f.endsWith('.json')
593
+ );
594
+
595
+ for (const file of embeddingFiles) {
596
+ const embeddingPath = path.join(embeddingsDir, file);
597
+
598
+ try {
599
+ const embeddingData = JSON.parse(
600
+ await fs.readFile(embeddingPath, 'utf-8')
601
+ );
602
+ const originalPath = embeddingData.originalPath;
603
+
604
+ if (originalPath && !existsSync(originalPath)) {
605
+ if (options.dryRun) {
606
+ console.log(chalk.blue(` [DRY-RUN] Would remove: ${file}`));
607
+ } else {
608
+ await fs.unlink(embeddingPath);
609
+ }
610
+ pruned++;
611
+ }
612
+ } catch {
613
+ // Skip files that can't be parsed
614
+ }
615
+ }
616
+ } catch {
617
+ // Ignore permission errors
618
+ }
619
+ }
620
+
621
+ if (!options.dryRun && pruned > 0) {
622
+ // Update prune metadata
623
+ const pruneMetadata = {
624
+ lastPrune: getTimestamp(),
625
+ prunedCount: pruned,
626
+ status: 'completed',
627
+ };
628
+
629
+ await fs.mkdir(path.join(storePath, 'metadata'), { recursive: true });
630
+ await fs.writeFile(
631
+ path.join(storePath, 'metadata', 'prune.json'),
632
+ JSON.stringify(pruneMetadata, null, 2)
633
+ );
634
+ }
635
+
636
+ console.log(chalk.green(` [OK] ${storeName}: Pruned ${pruned} entries`));
637
+ totalPruned += pruned;
638
+ }
639
+
640
+ console.log(chalk.gray('\n' + '-'.repeat(50)));
641
+ console.log(
642
+ chalk.green(`Prune complete: ${totalPruned} entries removed\n`)
643
+ );
644
+ } catch (error) {
645
+ spinner.fail('Failed to prune stores');
646
+ console.error(
647
+ chalk.red(error instanceof Error ? error.message : String(error))
648
+ );
649
+ }
650
+ }
651
+
652
+ async function reindexStores(options: {
653
+ store?: string;
654
+ force?: boolean;
655
+ }): Promise<void> {
656
+ const spinner = ora('Re-indexing RAG stores...').start();
657
+
658
+ try {
659
+ const stores = await getAllStores();
660
+
661
+ spinner.stop();
662
+ console.log(chalk.cyan('\nRe-indexing RAG Stores\n'));
663
+
664
+ for (const storePath of stores) {
665
+ const storeName = path.basename(storePath);
666
+
667
+ if (options.store && storeName !== options.store) {
668
+ continue;
669
+ }
670
+
671
+ const embeddingsDir = path.join(storePath, 'embeddings');
672
+ const indexesDir = path.join(storePath, 'indexes');
673
+
674
+ await fs.mkdir(indexesDir, { recursive: true });
675
+
676
+ let embeddingCount = 0;
677
+ if (existsSync(embeddingsDir)) {
678
+ try {
679
+ const files = readdirSync(embeddingsDir).filter(f =>
680
+ f.endsWith('.json')
681
+ );
682
+ embeddingCount = files.length;
683
+ } catch {
684
+ // Ignore permission errors
685
+ }
686
+ }
687
+
688
+ // Generate index file
689
+ const indexFile = path.join(indexesDir, 'main.json');
690
+ const indexData = {
691
+ version: '1.0.0',
692
+ created: getTimestamp(),
693
+ updated: getTimestamp(),
694
+ embeddingCount,
695
+ indexType: 'flat',
696
+ status: 'ready',
697
+ };
698
+
699
+ await fs.writeFile(indexFile, JSON.stringify(indexData, null, 2));
700
+
701
+ // Update index metadata
702
+ const indexMetadata = {
703
+ lastIndex: getTimestamp(),
704
+ totalEmbeddings: embeddingCount,
705
+ indexFile,
706
+ status: 'indexed',
707
+ };
708
+
709
+ await fs.mkdir(path.join(storePath, 'metadata'), { recursive: true });
710
+ await fs.writeFile(
711
+ path.join(storePath, 'metadata', 'index.json'),
712
+ JSON.stringify(indexMetadata, null, 2)
713
+ );
714
+
715
+ console.log(
716
+ chalk.green(` [OK] ${storeName}: Indexed ${embeddingCount} entries`)
717
+ );
718
+ }
719
+
720
+ console.log(chalk.gray('\n' + '-'.repeat(50)));
721
+ console.log(chalk.green('Re-index complete\n'));
722
+ } catch (error) {
723
+ spinner.fail('Failed to reindex stores');
724
+ console.error(
725
+ chalk.red(error instanceof Error ? error.message : String(error))
726
+ );
727
+ }
728
+ }
729
+
730
+ async function createStore(
731
+ name: string,
732
+ options: { source?: string; global?: boolean }
733
+ ): Promise<void> {
734
+ const spinner = ora(`Creating RAG store: ${name}...`).start();
735
+
736
+ try {
737
+ const baseDir = options.global
738
+ ? RAG_GLOBAL_DIR
739
+ : path.join(RAG_PROJECT_DIR, name);
740
+
741
+ if (existsSync(baseDir)) {
742
+ spinner.fail(`Store already exists: ${name}`);
743
+ return;
744
+ }
745
+
746
+ // Get source directory
747
+ let sourceDir = options.source;
748
+
749
+ if (!sourceDir) {
750
+ spinner.stop();
751
+ const answers = await inquirer.prompt([
752
+ {
753
+ type: 'input',
754
+ name: 'sourceDir',
755
+ message: 'Enter source directory to index:',
756
+ default: process.cwd(),
757
+ validate: (input: string) => {
758
+ if (!existsSync(input)) {
759
+ return 'Directory does not exist';
760
+ }
761
+ return true;
762
+ },
763
+ },
764
+ ]);
765
+ sourceDir = answers.sourceDir;
766
+ spinner.start();
767
+ }
768
+
769
+ // Create store directories
770
+ await fs.mkdir(path.join(baseDir, 'embeddings'), { recursive: true });
771
+ await fs.mkdir(path.join(baseDir, 'indexes'), { recursive: true });
772
+ await fs.mkdir(path.join(baseDir, 'metadata'), { recursive: true });
773
+ await fs.mkdir(path.join(baseDir, 'cache'), { recursive: true });
774
+
775
+ // Create source metadata
776
+ const sourceMetadata = {
777
+ sourceDir,
778
+ created: getTimestamp(),
779
+ name,
780
+ type: options.global ? 'global' : 'project-specific',
781
+ };
782
+
783
+ await fs.writeFile(
784
+ path.join(baseDir, 'metadata', 'source.json'),
785
+ JSON.stringify(sourceMetadata, null, 2)
786
+ );
787
+
788
+ spinner.succeed(`RAG store created: ${name}`);
789
+ console.log(chalk.gray(` Location: ${baseDir}`));
790
+ console.log(chalk.gray(` Source: ${sourceDir}`));
791
+ console.log(chalk.green('\nRun "wundr rag sync" to sync the store.\n'));
792
+ } catch (error) {
793
+ spinner.fail('Failed to create store');
794
+ console.error(
795
+ chalk.red(error instanceof Error ? error.message : String(error))
796
+ );
797
+ }
798
+ }
799
+
800
+ async function runSetup(options: { skipApiKey?: boolean }): Promise<void> {
801
+ console.log(chalk.cyan('\nRAG Infrastructure Setup\n'));
802
+ console.log(
803
+ chalk.gray('This will set up the RAG infrastructure for Wundr.\n')
804
+ );
805
+
806
+ const spinner = ora('Checking prerequisites...').start();
807
+
808
+ try {
809
+ // Check Node.js version
810
+ const versionPart = process.version.slice(1).split('.')[0] ?? '0';
811
+ const nodeVersion = parseInt(versionPart, 10);
812
+ if (nodeVersion < 18) {
813
+ spinner.fail(`Node.js 18+ required. Current version: ${process.version}`);
814
+ return;
815
+ }
816
+ spinner.succeed('Node.js version OK');
817
+
818
+ // Install @google/genai
819
+ spinner.start('Checking @google/genai package...');
820
+ try {
821
+ require.resolve('@google/genai');
822
+ spinner.succeed('@google/genai is available');
823
+ } catch {
824
+ spinner.text = 'Installing @google/genai...';
825
+ const { execSync } = await import('child_process');
826
+ execSync('npm install -g @google/genai', { stdio: 'pipe' });
827
+ spinner.succeed('@google/genai installed');
828
+ }
829
+
830
+ // Configure API key
831
+ if (!options.skipApiKey) {
832
+ spinner.stop();
833
+ console.log(chalk.yellow('\nGEMINI_API_KEY Configuration'));
834
+ console.log(
835
+ chalk.gray(
836
+ 'Get your API key from: https://makersuite.google.com/app/apikey\n'
837
+ )
838
+ );
839
+
840
+ if (process.env.GEMINI_API_KEY) {
841
+ console.log(
842
+ chalk.green('GEMINI_API_KEY is already set in environment.\n')
843
+ );
844
+ } else {
845
+ console.log(chalk.yellow('GEMINI_API_KEY is not set.'));
846
+ console.log(chalk.gray('Add the following to your shell profile:'));
847
+ console.log(
848
+ chalk.white(' export GEMINI_API_KEY="your-api-key-here"\n')
849
+ );
850
+ }
851
+ }
852
+
853
+ // Create directory structure
854
+ spinner.start('Creating RAG store directories...');
855
+
856
+ await fs.mkdir(RAG_GLOBAL_DIR, { recursive: true });
857
+ await fs.mkdir(path.join(RAG_GLOBAL_DIR, 'embeddings'), {
858
+ recursive: true,
859
+ });
860
+ await fs.mkdir(path.join(RAG_GLOBAL_DIR, 'indexes'), { recursive: true });
861
+ await fs.mkdir(path.join(RAG_GLOBAL_DIR, 'metadata'), { recursive: true });
862
+ await fs.mkdir(path.join(RAG_GLOBAL_DIR, 'cache'), { recursive: true });
863
+ await fs.mkdir(RAG_PROJECT_DIR, { recursive: true });
864
+
865
+ spinner.succeed('RAG store directories created');
866
+
867
+ // Create config file
868
+ spinner.start('Creating configuration file...');
869
+
870
+ if (!existsSync(CONFIG_FILE)) {
871
+ const config = getDefaultConfig();
872
+ await saveConfig(config);
873
+ spinner.succeed('Configuration file created');
874
+ } else {
875
+ spinner.succeed('Configuration file already exists');
876
+ }
877
+
878
+ console.log(chalk.green('\nRAG infrastructure setup complete!\n'));
879
+ console.log(chalk.gray('Next steps:'));
880
+ console.log(chalk.white(' 1. Set GEMINI_API_KEY if not already set'));
881
+ console.log(
882
+ chalk.white(' 2. Run "wundr rag create <name>" to create a store')
883
+ );
884
+ console.log(chalk.white(' 3. Run "wundr rag sync" to sync your stores'));
885
+ console.log(chalk.white(' 4. Run "wundr rag status" to check status\n'));
886
+ } catch (error) {
887
+ spinner.fail('Setup failed');
888
+ console.error(
889
+ chalk.red(error instanceof Error ? error.message : String(error))
890
+ );
891
+ }
892
+ }
893
+
894
+ async function manageConfig(options: {
895
+ get?: string;
896
+ set?: string;
897
+ reset?: boolean;
898
+ }): Promise<void> {
899
+ try {
900
+ if (options.reset) {
901
+ const config = getDefaultConfig();
902
+ await saveConfig(config);
903
+ console.log(chalk.green('Configuration reset to defaults.'));
904
+ return;
905
+ }
906
+
907
+ const config = await loadConfig();
908
+
909
+ if (options.get) {
910
+ const keys = options.get.split('.');
911
+ let value: unknown = config;
912
+ for (const key of keys) {
913
+ value = (value as Record<string, unknown>)[key];
914
+ }
915
+ console.log(JSON.stringify(value, null, 2));
916
+ return;
917
+ }
918
+
919
+ if (options.set) {
920
+ const [keyPath, ...valueParts] = options.set.split('=');
921
+ const value = valueParts.join('=');
922
+
923
+ if (!keyPath) {
924
+ console.error(chalk.red('Invalid key path'));
925
+ return;
926
+ }
927
+
928
+ const keys = keyPath.split('.');
929
+ let obj: Record<string, unknown> = config as unknown as Record<
930
+ string,
931
+ unknown
932
+ >;
933
+ for (let i = 0; i < keys.length - 1; i++) {
934
+ const key = keys[i];
935
+ if (key) {
936
+ obj = obj[key] as Record<string, unknown>;
937
+ }
938
+ }
939
+ const lastKey = keys[keys.length - 1];
940
+
941
+ if (!lastKey) {
942
+ console.error(chalk.red('Invalid key path'));
943
+ return;
944
+ }
945
+
946
+ // Parse value
947
+ try {
948
+ obj[lastKey] = JSON.parse(value);
949
+ } catch {
950
+ obj[lastKey] = value;
951
+ }
952
+
953
+ await saveConfig(config);
954
+ console.log(chalk.green(`Set ${keyPath} = ${value}`));
955
+ return;
956
+ }
957
+
958
+ // Show current config
959
+ console.log(chalk.cyan('\nRAG Configuration\n'));
960
+ console.log(JSON.stringify(config, null, 2));
961
+ } catch (error) {
962
+ console.error(
963
+ chalk.red(error instanceof Error ? error.message : String(error))
964
+ );
965
+ }
966
+ }
967
+
968
+ async function deleteStore(
969
+ name: string,
970
+ options: { force?: boolean }
971
+ ): Promise<void> {
972
+ const storePath =
973
+ name === 'global' ? RAG_GLOBAL_DIR : path.join(RAG_PROJECT_DIR, name);
974
+
975
+ if (!existsSync(storePath)) {
976
+ console.log(chalk.red(`Store not found: ${name}`));
977
+ return;
978
+ }
979
+
980
+ if (!options.force) {
981
+ const answers = await inquirer.prompt([
982
+ {
983
+ type: 'confirm',
984
+ name: 'confirm',
985
+ message: `Are you sure you want to delete store "${name}"?`,
986
+ default: false,
987
+ },
988
+ ]);
989
+
990
+ if (!answers.confirm) {
991
+ console.log(chalk.yellow('Cancelled.'));
992
+ return;
993
+ }
994
+ }
995
+
996
+ const spinner = ora(`Deleting store: ${name}...`).start();
997
+
998
+ try {
999
+ await fs.rm(storePath, { recursive: true, force: true });
1000
+ spinner.succeed(`Store deleted: ${name}`);
1001
+ } catch (error) {
1002
+ spinner.fail('Failed to delete store');
1003
+ console.error(
1004
+ chalk.red(error instanceof Error ? error.message : String(error))
1005
+ );
1006
+ }
1007
+ }
1008
+
1009
+ function padRight(str: string, length: number): string {
1010
+ return str.length >= length ? str : str + ' '.repeat(length - str.length);
1011
+ }