@wundr.io/cli 1.0.1 → 1.0.3

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