@wundr.io/cli 1.0.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 (213) hide show
  1. package/README.md +551 -0
  2. package/bin/wundr.js +39 -0
  3. package/dist/ai/ai-service.d.ts +152 -0
  4. package/dist/ai/ai-service.d.ts.map +1 -0
  5. package/dist/ai/ai-service.js +430 -0
  6. package/dist/ai/ai-service.js.map +1 -0
  7. package/dist/ai/claude-client.d.ts +130 -0
  8. package/dist/ai/claude-client.d.ts.map +1 -0
  9. package/dist/ai/claude-client.js +339 -0
  10. package/dist/ai/claude-client.js.map +1 -0
  11. package/dist/ai/conversation-manager.d.ts +164 -0
  12. package/dist/ai/conversation-manager.d.ts.map +1 -0
  13. package/dist/ai/conversation-manager.js +612 -0
  14. package/dist/ai/conversation-manager.js.map +1 -0
  15. package/dist/ai/index.d.ts +5 -0
  16. package/dist/ai/index.d.ts.map +1 -0
  17. package/dist/ai/index.js +8 -0
  18. package/dist/ai/index.js.map +1 -0
  19. package/dist/cli.d.ts +36 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +173 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands/ai.d.ts +89 -0
  24. package/dist/commands/ai.d.ts.map +1 -0
  25. package/dist/commands/ai.js +735 -0
  26. package/dist/commands/ai.js.map +1 -0
  27. package/dist/commands/analyze-optimized.d.ts +14 -0
  28. package/dist/commands/analyze-optimized.d.ts.map +1 -0
  29. package/dist/commands/analyze-optimized.js +437 -0
  30. package/dist/commands/analyze-optimized.js.map +1 -0
  31. package/dist/commands/analyze.d.ts +65 -0
  32. package/dist/commands/analyze.d.ts.map +1 -0
  33. package/dist/commands/analyze.js +435 -0
  34. package/dist/commands/analyze.js.map +1 -0
  35. package/dist/commands/batch.d.ts +71 -0
  36. package/dist/commands/batch.d.ts.map +1 -0
  37. package/dist/commands/batch.js +738 -0
  38. package/dist/commands/batch.js.map +1 -0
  39. package/dist/commands/chat.d.ts +71 -0
  40. package/dist/commands/chat.d.ts.map +1 -0
  41. package/dist/commands/chat.js +674 -0
  42. package/dist/commands/chat.js.map +1 -0
  43. package/dist/commands/claude-init.d.ts +28 -0
  44. package/dist/commands/claude-init.d.ts.map +1 -0
  45. package/dist/commands/claude-init.js +587 -0
  46. package/dist/commands/claude-init.js.map +1 -0
  47. package/dist/commands/claude-setup.d.ts +32 -0
  48. package/dist/commands/claude-setup.d.ts.map +1 -0
  49. package/dist/commands/claude-setup.js +570 -0
  50. package/dist/commands/claude-setup.js.map +1 -0
  51. package/dist/commands/computer-setup-commands.d.ts +39 -0
  52. package/dist/commands/computer-setup-commands.d.ts.map +1 -0
  53. package/dist/commands/computer-setup-commands.js +563 -0
  54. package/dist/commands/computer-setup-commands.js.map +1 -0
  55. package/dist/commands/computer-setup.d.ts +7 -0
  56. package/dist/commands/computer-setup.d.ts.map +1 -0
  57. package/dist/commands/computer-setup.js +481 -0
  58. package/dist/commands/computer-setup.js.map +1 -0
  59. package/dist/commands/create-command.d.ts +7 -0
  60. package/dist/commands/create-command.d.ts.map +1 -0
  61. package/dist/commands/create-command.js +158 -0
  62. package/dist/commands/create-command.js.map +1 -0
  63. package/dist/commands/create.d.ts +74 -0
  64. package/dist/commands/create.d.ts.map +1 -0
  65. package/dist/commands/create.js +556 -0
  66. package/dist/commands/create.js.map +1 -0
  67. package/dist/commands/dashboard.d.ts +91 -0
  68. package/dist/commands/dashboard.d.ts.map +1 -0
  69. package/dist/commands/dashboard.js +537 -0
  70. package/dist/commands/dashboard.js.map +1 -0
  71. package/dist/commands/govern.d.ts +70 -0
  72. package/dist/commands/govern.d.ts.map +1 -0
  73. package/dist/commands/govern.js +480 -0
  74. package/dist/commands/govern.js.map +1 -0
  75. package/dist/commands/init.d.ts +55 -0
  76. package/dist/commands/init.d.ts.map +1 -0
  77. package/dist/commands/init.js +584 -0
  78. package/dist/commands/init.js.map +1 -0
  79. package/dist/commands/performance-optimizer.d.ts +30 -0
  80. package/dist/commands/performance-optimizer.d.ts.map +1 -0
  81. package/dist/commands/performance-optimizer.js +649 -0
  82. package/dist/commands/performance-optimizer.js.map +1 -0
  83. package/dist/commands/plugins.d.ts +87 -0
  84. package/dist/commands/plugins.d.ts.map +1 -0
  85. package/dist/commands/plugins.js +685 -0
  86. package/dist/commands/plugins.js.map +1 -0
  87. package/dist/commands/setup.d.ts +29 -0
  88. package/dist/commands/setup.d.ts.map +1 -0
  89. package/dist/commands/setup.js +399 -0
  90. package/dist/commands/setup.js.map +1 -0
  91. package/dist/commands/test-init.d.ts +9 -0
  92. package/dist/commands/test-init.d.ts.map +1 -0
  93. package/dist/commands/test-init.js +222 -0
  94. package/dist/commands/test-init.js.map +1 -0
  95. package/dist/commands/test.d.ts +25 -0
  96. package/dist/commands/test.d.ts.map +1 -0
  97. package/dist/commands/test.js +217 -0
  98. package/dist/commands/test.js.map +1 -0
  99. package/dist/commands/watch.d.ts +76 -0
  100. package/dist/commands/watch.d.ts.map +1 -0
  101. package/dist/commands/watch.js +610 -0
  102. package/dist/commands/watch.js.map +1 -0
  103. package/dist/context/context-manager.d.ts +155 -0
  104. package/dist/context/context-manager.d.ts.map +1 -0
  105. package/dist/context/context-manager.js +383 -0
  106. package/dist/context/context-manager.js.map +1 -0
  107. package/dist/context/index.d.ts +3 -0
  108. package/dist/context/index.d.ts.map +1 -0
  109. package/dist/context/index.js +6 -0
  110. package/dist/context/index.js.map +1 -0
  111. package/dist/context/session-manager.d.ts +207 -0
  112. package/dist/context/session-manager.d.ts.map +1 -0
  113. package/dist/context/session-manager.js +682 -0
  114. package/dist/context/session-manager.js.map +1 -0
  115. package/dist/index.d.ts +8 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +51 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/interactive/interactive-mode.d.ts +76 -0
  120. package/dist/interactive/interactive-mode.d.ts.map +1 -0
  121. package/dist/interactive/interactive-mode.js +730 -0
  122. package/dist/interactive/interactive-mode.js.map +1 -0
  123. package/dist/nlp/command-mapper.d.ts +174 -0
  124. package/dist/nlp/command-mapper.d.ts.map +1 -0
  125. package/dist/nlp/command-mapper.js +623 -0
  126. package/dist/nlp/command-mapper.js.map +1 -0
  127. package/dist/nlp/command-parser.d.ts +106 -0
  128. package/dist/nlp/command-parser.d.ts.map +1 -0
  129. package/dist/nlp/command-parser.js +416 -0
  130. package/dist/nlp/command-parser.js.map +1 -0
  131. package/dist/nlp/index.d.ts +5 -0
  132. package/dist/nlp/index.d.ts.map +1 -0
  133. package/dist/nlp/index.js +8 -0
  134. package/dist/nlp/index.js.map +1 -0
  135. package/dist/nlp/intent-classifier.d.ts +59 -0
  136. package/dist/nlp/intent-classifier.d.ts.map +1 -0
  137. package/dist/nlp/intent-classifier.js +384 -0
  138. package/dist/nlp/intent-classifier.js.map +1 -0
  139. package/dist/nlp/intent-parser.d.ts +152 -0
  140. package/dist/nlp/intent-parser.d.ts.map +1 -0
  141. package/dist/nlp/intent-parser.js +739 -0
  142. package/dist/nlp/intent-parser.js.map +1 -0
  143. package/dist/plugins/plugin-manager.d.ts +120 -0
  144. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  145. package/dist/plugins/plugin-manager.js +595 -0
  146. package/dist/plugins/plugin-manager.js.map +1 -0
  147. package/dist/types/index.d.ts +224 -0
  148. package/dist/types/index.d.ts.map +1 -0
  149. package/dist/types/index.js +3 -0
  150. package/dist/types/index.js.map +1 -0
  151. package/dist/utils/config-manager.d.ts +73 -0
  152. package/dist/utils/config-manager.d.ts.map +1 -0
  153. package/dist/utils/config-manager.js +339 -0
  154. package/dist/utils/config-manager.js.map +1 -0
  155. package/dist/utils/error-handler.d.ts +46 -0
  156. package/dist/utils/error-handler.d.ts.map +1 -0
  157. package/dist/utils/error-handler.js +169 -0
  158. package/dist/utils/error-handler.js.map +1 -0
  159. package/dist/utils/logger.d.ts +25 -0
  160. package/dist/utils/logger.d.ts.map +1 -0
  161. package/dist/utils/logger.js +94 -0
  162. package/dist/utils/logger.js.map +1 -0
  163. package/package.json +119 -0
  164. package/src/ai/ai-service.ts +595 -0
  165. package/src/ai/claude-client.ts +490 -0
  166. package/src/ai/conversation-manager.ts +907 -0
  167. package/src/ai/index.ts +8 -0
  168. package/src/cli.ts +202 -0
  169. package/src/commands/ai.ts +995 -0
  170. package/src/commands/analyze-optimized.ts +641 -0
  171. package/src/commands/analyze.ts +576 -0
  172. package/src/commands/batch.ts +935 -0
  173. package/src/commands/chat.ts +876 -0
  174. package/src/commands/claude-init.ts +715 -0
  175. package/src/commands/claude-setup.ts +697 -0
  176. package/src/commands/computer-setup-commands.ts +709 -0
  177. package/src/commands/computer-setup.ts +565 -0
  178. package/src/commands/create-command.ts +175 -0
  179. package/src/commands/create.ts +727 -0
  180. package/src/commands/dashboard.ts +691 -0
  181. package/src/commands/govern.ts +635 -0
  182. package/src/commands/init.ts +677 -0
  183. package/src/commands/performance-optimizer.ts +864 -0
  184. package/src/commands/plugins.ts +848 -0
  185. package/src/commands/setup.ts +508 -0
  186. package/src/commands/test-init.ts +242 -0
  187. package/src/commands/test.ts +264 -0
  188. package/src/commands/watch.ts +755 -0
  189. package/src/context/context-manager.ts +546 -0
  190. package/src/context/index.ts +9 -0
  191. package/src/context/session-manager.ts +1019 -0
  192. package/src/index.ts +64 -0
  193. package/src/interactive/interactive-mode.ts +830 -0
  194. package/src/nlp/command-mapper.ts +885 -0
  195. package/src/nlp/command-parser.ts +564 -0
  196. package/src/nlp/index.ts +4 -0
  197. package/src/nlp/intent-classifier.ts +458 -0
  198. package/src/nlp/intent-parser.ts +1101 -0
  199. package/src/plugins/plugin-manager.ts +744 -0
  200. package/src/types/index.ts +252 -0
  201. package/src/types/modules.d.ts +56 -0
  202. package/src/utils/config-manager.ts +391 -0
  203. package/src/utils/error-handler.ts +192 -0
  204. package/src/utils/logger.ts +104 -0
  205. package/templates/batch/ci-cd.yaml +62 -0
  206. package/templates/component/{{fileName}}.test.tsx +17 -0
  207. package/templates/component/{{fileName}}.tsx +21 -0
  208. package/templates/service/{{fileName}}.ts +98 -0
  209. package/templates/wundr-test.config.js +0 -0
  210. package/test-suites/api/health.spec.ts +134 -0
  211. package/test-suites/helpers/test-config.ts +84 -0
  212. package/test-suites/ui/accessibility.spec.ts +102 -0
  213. package/test-suites/ui/smoke.spec.ts +92 -0
@@ -0,0 +1,885 @@
1
+ import { EventEmitter } from 'events';
2
+ import { spawn, ChildProcess } from 'child_process';
3
+ import { logger } from '../utils/logger';
4
+ import { IntentResult } from './intent-parser';
5
+
6
+ /**
7
+ * Command execution result
8
+ */
9
+ export interface CommandResult {
10
+ command: string;
11
+ success: boolean;
12
+ output: string;
13
+ error?: string;
14
+ exitCode: number;
15
+ executionTime: number;
16
+ metadata?: Record<string, any>;
17
+ }
18
+
19
+ /**
20
+ * Command validation result
21
+ */
22
+ export interface ValidationResult {
23
+ valid: boolean;
24
+ errors: string[];
25
+ warnings: string[];
26
+ suggestions: string[];
27
+ safetyLevel: 'safe' | 'caution' | 'dangerous';
28
+ }
29
+
30
+ /**
31
+ * Command mapping configuration
32
+ */
33
+ export interface CommandMapperConfig {
34
+ dryRun: boolean;
35
+ confirmDestructive: boolean;
36
+ timeout: number; // milliseconds
37
+ maxConcurrentCommands: number;
38
+ enableLogging: boolean;
39
+ safetyChecks: boolean;
40
+ }
41
+
42
+ /**
43
+ * Command template for mapping intents to executable commands
44
+ */
45
+ export interface CommandTemplate {
46
+ intent: string;
47
+ commandTemplate: string;
48
+ parameterMapping: ParameterMapping[];
49
+ validation: ValidationRule[];
50
+ safetyLevel: 'safe' | 'caution' | 'dangerous';
51
+ requiresConfirmation: boolean;
52
+ examples: CommandExample[];
53
+ dependencies?: string[]; // Required tools/commands
54
+ category: string;
55
+ }
56
+
57
+ /**
58
+ * Parameter mapping from intent to command
59
+ */
60
+ export interface ParameterMapping {
61
+ intentParam: string;
62
+ commandFlag: string;
63
+ transform?: (value: any) => string;
64
+ validation?: (value: any) => boolean;
65
+ required?: boolean;
66
+ defaultValue?: string;
67
+ }
68
+
69
+ /**
70
+ * Validation rule for commands
71
+ */
72
+ export interface ValidationRule {
73
+ type:
74
+ | 'parameter'
75
+ | 'file_exists'
76
+ | 'directory_exists'
77
+ | 'command_available'
78
+ | 'custom';
79
+ rule: string | ((params: Record<string, any>) => Promise<boolean>);
80
+ message: string;
81
+ severity: 'error' | 'warning';
82
+ }
83
+
84
+ /**
85
+ * Command example for documentation
86
+ */
87
+ export interface CommandExample {
88
+ description: string;
89
+ intentInput: string;
90
+ expectedCommand: string;
91
+ }
92
+
93
+ /**
94
+ * Execution context for commands
95
+ */
96
+ export interface ExecutionContext {
97
+ workingDirectory: string;
98
+ environment: Record<string, string>;
99
+ user: string;
100
+ interactive: boolean;
101
+ dryRun: boolean;
102
+ }
103
+
104
+ /**
105
+ * Command mapper that converts parsed intents into executable CLI commands
106
+ */
107
+ export class CommandMapper extends EventEmitter {
108
+ private config: CommandMapperConfig;
109
+ private commandTemplates: Map<string, CommandTemplate>;
110
+ private runningCommands: Map<string, ChildProcess>;
111
+ private commandHistory: CommandResult[];
112
+
113
+ constructor(config: Partial<CommandMapperConfig> = {}) {
114
+ super();
115
+
116
+ this.config = {
117
+ dryRun: false,
118
+ confirmDestructive: true,
119
+ timeout: 300000, // 5 minutes
120
+ maxConcurrentCommands: 5,
121
+ enableLogging: true,
122
+ safetyChecks: true,
123
+ ...config,
124
+ };
125
+
126
+ this.commandTemplates = new Map();
127
+ this.runningCommands = new Map();
128
+ this.commandHistory = [];
129
+
130
+ this.initializeDefaultTemplates();
131
+ }
132
+
133
+ /**
134
+ * Map an intent result to an executable command
135
+ */
136
+ async mapIntentToCommand(
137
+ intentResult: IntentResult,
138
+ context: ExecutionContext
139
+ ): Promise<{
140
+ command: string;
141
+ args: string[];
142
+ validation: ValidationResult;
143
+ safetyLevel: 'safe' | 'caution' | 'dangerous';
144
+ requiresConfirmation: boolean;
145
+ }> {
146
+ const template = this.commandTemplates.get(intentResult.intent);
147
+
148
+ if (!template) {
149
+ throw new Error(
150
+ `No command template found for intent: ${intentResult.intent}`
151
+ );
152
+ }
153
+
154
+ // Build command from template
155
+ const { command, args } = await this.buildCommand(
156
+ template,
157
+ intentResult.parameters || {},
158
+ context
159
+ );
160
+
161
+ // Validate command
162
+ const validation = await this.validateCommand(
163
+ template,
164
+ intentResult.parameters || {},
165
+ context
166
+ );
167
+
168
+ this.emit('command_mapped', {
169
+ intent: intentResult.intent,
170
+ command,
171
+ args,
172
+ validation,
173
+ template: template.intent,
174
+ });
175
+
176
+ return {
177
+ command,
178
+ args,
179
+ validation,
180
+ safetyLevel: template.safetyLevel,
181
+ requiresConfirmation: template.requiresConfirmation,
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Execute a mapped command
187
+ */
188
+ async executeCommand(
189
+ command: string,
190
+ args: string[],
191
+ context: ExecutionContext,
192
+ options: {
193
+ streaming?: boolean;
194
+ onOutput?: (output: string) => void;
195
+ onError?: (error: string) => void;
196
+ } = {}
197
+ ): Promise<CommandResult> {
198
+ const startTime = Date.now();
199
+ const executionId = `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
200
+
201
+ if (context.dryRun || this.config.dryRun) {
202
+ logger.info(`[DRY RUN] Would execute: ${command} ${args.join(' ')}`);
203
+ return {
204
+ command: `${command} ${args.join(' ')}`,
205
+ success: true,
206
+ output: '[DRY RUN] Command would execute successfully',
207
+ exitCode: 0,
208
+ executionTime: 0,
209
+ metadata: { dryRun: true },
210
+ };
211
+ }
212
+
213
+ return new Promise((resolve, reject) => {
214
+ const childProcess = spawn(command, args, {
215
+ cwd: context.workingDirectory,
216
+ env: { ...process.env, ...context.environment },
217
+ stdio: options.streaming ? 'pipe' : 'pipe',
218
+ });
219
+
220
+ this.runningCommands.set(executionId, childProcess);
221
+
222
+ let output = '';
223
+ let errorOutput = '';
224
+
225
+ // Handle stdout
226
+ if (childProcess.stdout) {
227
+ childProcess.stdout.on('data', (data: Buffer) => {
228
+ const chunk = data.toString();
229
+ output += chunk;
230
+
231
+ if (options.onOutput) {
232
+ options.onOutput(chunk);
233
+ }
234
+
235
+ if (options.streaming) {
236
+ this.emit('command_output', {
237
+ executionId,
238
+ chunk,
239
+ stream: 'stdout',
240
+ });
241
+ }
242
+ });
243
+ }
244
+
245
+ // Handle stderr
246
+ if (childProcess.stderr) {
247
+ childProcess.stderr.on('data', (data: Buffer) => {
248
+ const chunk = data.toString();
249
+ errorOutput += chunk;
250
+
251
+ if (options.onError) {
252
+ options.onError(chunk);
253
+ }
254
+
255
+ if (options.streaming) {
256
+ this.emit('command_output', {
257
+ executionId,
258
+ chunk,
259
+ stream: 'stderr',
260
+ });
261
+ }
262
+ });
263
+ }
264
+
265
+ // Handle process exit
266
+ childProcess.on('close', (code: number | null) => {
267
+ const endTime = Date.now();
268
+ const executionTime = endTime - startTime;
269
+
270
+ this.runningCommands.delete(executionId);
271
+
272
+ const result: CommandResult = {
273
+ command: `${command} ${args.join(' ')}`,
274
+ success: (code || 0) === 0,
275
+ output,
276
+ error: errorOutput || undefined,
277
+ exitCode: code || 0,
278
+ executionTime,
279
+ metadata: {
280
+ executionId,
281
+ workingDirectory: context.workingDirectory,
282
+ startTime: new Date(startTime),
283
+ endTime: new Date(endTime),
284
+ },
285
+ };
286
+
287
+ // Log result
288
+ if (this.config.enableLogging) {
289
+ logger.debug(`Command executed: ${result.command}`, {
290
+ success: result.success,
291
+ exitCode: result.exitCode,
292
+ executionTime: result.executionTime,
293
+ });
294
+ }
295
+
296
+ // Add to history
297
+ this.commandHistory.push(result);
298
+
299
+ // Limit history size
300
+ if (this.commandHistory.length > 1000) {
301
+ this.commandHistory = this.commandHistory.slice(-500);
302
+ }
303
+
304
+ this.emit('command_completed', { executionId, result });
305
+
306
+ resolve(result);
307
+ });
308
+
309
+ // Handle process errors
310
+ childProcess.on('error', (error: Error) => {
311
+ this.runningCommands.delete(executionId);
312
+
313
+ const result: CommandResult = {
314
+ command: `${command} ${args.join(' ')}`,
315
+ success: false,
316
+ output,
317
+ error: error.message,
318
+ exitCode: -1,
319
+ executionTime: Date.now() - startTime,
320
+ metadata: { executionId, error: error.message },
321
+ };
322
+
323
+ this.emit('command_error', { executionId, error });
324
+ resolve(result); // Don't reject, return error result instead
325
+ });
326
+
327
+ // Set timeout
328
+ if (this.config.timeout > 0) {
329
+ setTimeout(() => {
330
+ if (this.runningCommands.has(executionId)) {
331
+ childProcess.kill('SIGTERM');
332
+ logger.warn(
333
+ `Command timed out after ${this.config.timeout}ms: ${command}`
334
+ );
335
+ }
336
+ }, this.config.timeout);
337
+ }
338
+ });
339
+ }
340
+
341
+ /**
342
+ * Execute a command from an intent result
343
+ */
344
+ async executeFromIntent(
345
+ intentResult: IntentResult,
346
+ context: ExecutionContext,
347
+ options: {
348
+ skipConfirmation?: boolean;
349
+ streaming?: boolean;
350
+ onOutput?: (output: string) => void;
351
+ onError?: (error: string) => void;
352
+ } = {}
353
+ ): Promise<CommandResult> {
354
+ // Map intent to command
355
+ const mappedCommand = await this.mapIntentToCommand(intentResult, context);
356
+
357
+ // Check validation
358
+ if (!mappedCommand.validation.valid) {
359
+ throw new Error(
360
+ `Command validation failed: ${mappedCommand.validation.errors.join(', ')}`
361
+ );
362
+ }
363
+
364
+ // Handle confirmation for dangerous commands
365
+ if (
366
+ mappedCommand.requiresConfirmation &&
367
+ this.config.confirmDestructive &&
368
+ !options.skipConfirmation &&
369
+ context.interactive
370
+ ) {
371
+ const confirmed = await this.confirmExecution(
372
+ mappedCommand,
373
+ intentResult
374
+ );
375
+ if (!confirmed) {
376
+ throw new Error('Command execution cancelled by user');
377
+ }
378
+ }
379
+
380
+ // Execute command
381
+ return this.executeCommand(
382
+ mappedCommand.command,
383
+ mappedCommand.args,
384
+ context,
385
+ options
386
+ );
387
+ }
388
+
389
+ /**
390
+ * Register a custom command template
391
+ */
392
+ registerCommandTemplate(template: CommandTemplate): void {
393
+ this.commandTemplates.set(template.intent, template);
394
+ logger.debug(`Registered command template: ${template.intent}`);
395
+ this.emit('template_registered', template);
396
+ }
397
+
398
+ /**
399
+ * Validate a command before execution
400
+ */
401
+ async validateCommand(
402
+ template: CommandTemplate,
403
+ parameters: Record<string, any>,
404
+ context: ExecutionContext
405
+ ): Promise<ValidationResult> {
406
+ const result: ValidationResult = {
407
+ valid: true,
408
+ errors: [],
409
+ warnings: [],
410
+ suggestions: [],
411
+ safetyLevel: template.safetyLevel,
412
+ };
413
+
414
+ if (!this.config.safetyChecks) {
415
+ return result;
416
+ }
417
+
418
+ // Run validation rules
419
+ for (const rule of template.validation) {
420
+ try {
421
+ const isValid = await this.executeValidationRule(
422
+ rule,
423
+ parameters,
424
+ context
425
+ );
426
+
427
+ if (!isValid) {
428
+ if (rule.severity === 'error') {
429
+ result.errors.push(rule.message);
430
+ result.valid = false;
431
+ } else {
432
+ result.warnings.push(rule.message);
433
+ }
434
+ }
435
+ } catch (error) {
436
+ result.errors.push(
437
+ `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`
438
+ );
439
+ result.valid = false;
440
+ }
441
+ }
442
+
443
+ // Check parameter requirements
444
+ for (const mapping of template.parameterMapping) {
445
+ if (mapping.required && !parameters[mapping.intentParam]) {
446
+ result.errors.push(
447
+ `Required parameter missing: ${mapping.intentParam}`
448
+ );
449
+ result.valid = false;
450
+ }
451
+
452
+ if (parameters[mapping.intentParam] && mapping.validation) {
453
+ if (!mapping.validation(parameters[mapping.intentParam])) {
454
+ result.errors.push(
455
+ `Invalid value for parameter: ${mapping.intentParam}`
456
+ );
457
+ result.valid = false;
458
+ }
459
+ }
460
+ }
461
+
462
+ // Check dependencies
463
+ if (template.dependencies) {
464
+ for (const dep of template.dependencies) {
465
+ const available = await this.checkCommandAvailable(dep);
466
+ if (!available) {
467
+ result.errors.push(`Required dependency not available: ${dep}`);
468
+ result.valid = false;
469
+ }
470
+ }
471
+ }
472
+
473
+ return result;
474
+ }
475
+
476
+ /**
477
+ * Get command execution history
478
+ */
479
+ getCommandHistory(
480
+ filters: {
481
+ limit?: number;
482
+ successOnly?: boolean;
483
+ intent?: string;
484
+ since?: Date;
485
+ } = {}
486
+ ): CommandResult[] {
487
+ let history = [...this.commandHistory];
488
+
489
+ if (filters.since) {
490
+ history = history.filter(
491
+ cmd =>
492
+ cmd.metadata?.['startTime'] &&
493
+ new Date(cmd.metadata['startTime']) >= filters.since!
494
+ );
495
+ }
496
+
497
+ if (filters.successOnly) {
498
+ history = history.filter(cmd => cmd.success);
499
+ }
500
+
501
+ if (filters.intent) {
502
+ history = history.filter(
503
+ cmd => cmd.metadata?.['intent'] === filters.intent
504
+ );
505
+ }
506
+
507
+ if (filters.limit) {
508
+ history = history.slice(-filters.limit);
509
+ }
510
+
511
+ return history;
512
+ }
513
+
514
+ /**
515
+ * Cancel a running command
516
+ */
517
+ async cancelCommand(executionId: string): Promise<boolean> {
518
+ const childProcess = this.runningCommands.get(executionId);
519
+
520
+ if (childProcess) {
521
+ childProcess.kill('SIGTERM');
522
+
523
+ // Give it a moment to terminate gracefully
524
+ await new Promise(resolve => setTimeout(resolve, 2000));
525
+
526
+ // Force kill if still running
527
+ if (!childProcess.killed) {
528
+ childProcess.kill('SIGKILL');
529
+ }
530
+
531
+ this.runningCommands.delete(executionId);
532
+ this.emit('command_cancelled', { executionId });
533
+
534
+ return true;
535
+ }
536
+
537
+ return false;
538
+ }
539
+
540
+ /**
541
+ * Get currently running commands
542
+ */
543
+ getRunningCommands(): Array<{
544
+ executionId: string;
545
+ pid: number;
546
+ command: string;
547
+ }> {
548
+ return Array.from(this.runningCommands.entries()).map(([id, process]) => ({
549
+ executionId: id,
550
+ pid: process.pid || 0,
551
+ command: process.spawnargs.join(' '),
552
+ }));
553
+ }
554
+
555
+ /**
556
+ * Generate command preview without executing
557
+ */
558
+ async previewCommand(
559
+ intentResult: IntentResult,
560
+ context: ExecutionContext
561
+ ): Promise<{
562
+ command: string;
563
+ explanation: string;
564
+ warnings: string[];
565
+ safetyLevel: string;
566
+ }> {
567
+ const mappedCommand = await this.mapIntentToCommand(intentResult, context);
568
+
569
+ const explanation = this.generateCommandExplanation(
570
+ mappedCommand.command,
571
+ mappedCommand.args,
572
+ intentResult
573
+ );
574
+
575
+ return {
576
+ command: `${mappedCommand.command} ${mappedCommand.args.join(' ')}`,
577
+ explanation,
578
+ warnings: mappedCommand.validation.warnings,
579
+ safetyLevel: mappedCommand.safetyLevel,
580
+ };
581
+ }
582
+
583
+ // Private methods
584
+
585
+ private initializeDefaultTemplates(): void {
586
+ const defaultTemplates: CommandTemplate[] = [
587
+ {
588
+ intent: 'analyze',
589
+ commandTemplate: 'wundr analyze',
590
+ parameterMapping: [
591
+ {
592
+ intentParam: 'path',
593
+ commandFlag: '--path',
594
+ defaultValue: '.',
595
+ validation: (value: string) => typeof value === 'string',
596
+ },
597
+ {
598
+ intentParam: 'focus',
599
+ commandFlag: '--focus',
600
+ validation: (value: string) =>
601
+ ['dependencies', 'quality', 'security', 'performance'].includes(
602
+ value
603
+ ),
604
+ },
605
+ {
606
+ intentParam: 'format',
607
+ commandFlag: '--format',
608
+ defaultValue: 'table',
609
+ validation: (value: string) =>
610
+ ['json', 'table', 'csv'].includes(value),
611
+ },
612
+ ],
613
+ validation: [
614
+ {
615
+ type: 'directory_exists',
616
+ rule: 'path',
617
+ message: 'Target directory does not exist',
618
+ severity: 'error',
619
+ },
620
+ ],
621
+ safetyLevel: 'safe',
622
+ requiresConfirmation: false,
623
+ examples: [
624
+ {
625
+ description: 'Analyze current directory',
626
+ intentInput: 'analyze the project',
627
+ expectedCommand: 'wundr analyze --path .',
628
+ },
629
+ ],
630
+ category: 'analysis',
631
+ },
632
+ {
633
+ intent: 'create',
634
+ commandTemplate: 'wundr create',
635
+ parameterMapping: [
636
+ {
637
+ intentParam: 'type',
638
+ commandFlag: '',
639
+ required: true,
640
+ validation: (value: string) =>
641
+ ['component', 'service', 'test', 'config'].includes(value),
642
+ },
643
+ {
644
+ intentParam: 'name',
645
+ commandFlag: '',
646
+ required: true,
647
+ validation: (value: string) =>
648
+ /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(value),
649
+ },
650
+ {
651
+ intentParam: 'template',
652
+ commandFlag: '--template',
653
+ },
654
+ ],
655
+ validation: [
656
+ {
657
+ type: 'custom',
658
+ rule: async params => {
659
+ // Check if file doesn't already exist
660
+ return true; // Simplified for example
661
+ },
662
+ message: 'File already exists',
663
+ severity: 'warning',
664
+ },
665
+ ],
666
+ safetyLevel: 'caution',
667
+ requiresConfirmation: false,
668
+ examples: [
669
+ {
670
+ description: 'Create a new component',
671
+ intentInput: 'create component UserProfile',
672
+ expectedCommand: 'wundr create component UserProfile',
673
+ },
674
+ ],
675
+ category: 'generation',
676
+ },
677
+ {
678
+ intent: 'init',
679
+ commandTemplate: 'wundr init',
680
+ parameterMapping: [
681
+ {
682
+ intentParam: 'project',
683
+ commandFlag: '',
684
+ defaultValue: '.',
685
+ },
686
+ {
687
+ intentParam: 'template',
688
+ commandFlag: '--template',
689
+ },
690
+ {
691
+ intentParam: 'force',
692
+ commandFlag: '--force',
693
+ transform: (value: boolean) => (value ? '--force' : ''),
694
+ },
695
+ ],
696
+ validation: [
697
+ {
698
+ type: 'directory_exists',
699
+ rule: 'project',
700
+ message: 'Target directory does not exist',
701
+ severity: 'error',
702
+ },
703
+ ],
704
+ safetyLevel: 'caution',
705
+ requiresConfirmation: true,
706
+ examples: [
707
+ {
708
+ description: 'Initialize new project',
709
+ intentInput: 'init new project',
710
+ expectedCommand: 'wundr init',
711
+ },
712
+ ],
713
+ category: 'setup',
714
+ },
715
+ {
716
+ intent: 'dashboard',
717
+ commandTemplate: 'wundr dashboard',
718
+ parameterMapping: [
719
+ {
720
+ intentParam: 'port',
721
+ commandFlag: '--port',
722
+ defaultValue: '3000',
723
+ validation: (value: number) => value > 0 && value < 65536,
724
+ },
725
+ {
726
+ intentParam: 'view',
727
+ commandFlag: '--view',
728
+ },
729
+ ],
730
+ validation: [],
731
+ safetyLevel: 'safe',
732
+ requiresConfirmation: false,
733
+ examples: [
734
+ {
735
+ description: 'Start dashboard',
736
+ intentInput: 'open dashboard',
737
+ expectedCommand: 'wundr dashboard --port 3000',
738
+ },
739
+ ],
740
+ category: 'interface',
741
+ },
742
+ ];
743
+
744
+ defaultTemplates.forEach(template =>
745
+ this.registerCommandTemplate(template)
746
+ );
747
+ }
748
+
749
+ private async buildCommand(
750
+ template: CommandTemplate,
751
+ parameters: Record<string, any>,
752
+ context: ExecutionContext
753
+ ): Promise<{ command: string; args: string[] }> {
754
+ const commandParts = template.commandTemplate.split(' ');
755
+ const baseCommand = commandParts[0] || '';
756
+ const args = commandParts.slice(1);
757
+
758
+ // Add parameter-based arguments
759
+ for (const mapping of template.parameterMapping) {
760
+ let value = parameters[mapping.intentParam];
761
+
762
+ // Apply default value
763
+ if (value === undefined && mapping.defaultValue !== undefined) {
764
+ value = mapping.defaultValue;
765
+ }
766
+
767
+ if (value !== undefined && value !== '') {
768
+ // Transform value if needed
769
+ if (mapping.transform) {
770
+ const transformed = mapping.transform(value);
771
+ if (transformed) {
772
+ if (mapping.commandFlag) {
773
+ args.push(mapping.commandFlag);
774
+ args.push(transformed);
775
+ } else {
776
+ args.push(transformed);
777
+ }
778
+ }
779
+ } else {
780
+ if (mapping.commandFlag) {
781
+ args.push(mapping.commandFlag);
782
+ args.push(String(value));
783
+ } else {
784
+ args.push(String(value));
785
+ }
786
+ }
787
+ }
788
+ }
789
+
790
+ return { command: baseCommand, args };
791
+ }
792
+
793
+ private async executeValidationRule(
794
+ rule: ValidationRule,
795
+ parameters: Record<string, any>,
796
+ context: ExecutionContext
797
+ ): Promise<boolean> {
798
+ switch (rule.type) {
799
+ case 'parameter':
800
+ return parameters[rule.rule as string] !== undefined;
801
+
802
+ case 'file_exists':
803
+ const fs = await import('fs-extra');
804
+ const filePath = parameters[rule.rule as string];
805
+ return filePath ? await fs.pathExists(filePath) : false;
806
+
807
+ case 'directory_exists':
808
+ const fsDir = await import('fs-extra');
809
+ const dirPath = parameters[rule.rule as string];
810
+ if (!dirPath) return false;
811
+ const stats = await fsDir.stat(dirPath).catch(() => null);
812
+ return stats ? stats.isDirectory() : false;
813
+
814
+ case 'command_available':
815
+ return this.checkCommandAvailable(rule.rule as string);
816
+
817
+ case 'custom':
818
+ if (typeof rule.rule === 'function') {
819
+ return rule.rule(parameters);
820
+ }
821
+ return false;
822
+
823
+ default:
824
+ return true;
825
+ }
826
+ }
827
+
828
+ private async checkCommandAvailable(command: string): Promise<boolean> {
829
+ return new Promise(resolve => {
830
+ const child = spawn('which', [command], { stdio: 'ignore' });
831
+ child.on('close', code => resolve(code === 0));
832
+ child.on('error', () => resolve(false));
833
+ });
834
+ }
835
+
836
+ private async confirmExecution(
837
+ mappedCommand: any,
838
+ intentResult: IntentResult
839
+ ): Promise<boolean> {
840
+ // In a real implementation, this would show a confirmation dialog
841
+ // For now, we'll assume confirmation based on safety level
842
+
843
+ if (mappedCommand.safetyLevel === 'dangerous') {
844
+ logger.warn(
845
+ `Dangerous command requires confirmation: ${mappedCommand.command} ${mappedCommand.args.join(' ')}`
846
+ );
847
+ // In a real CLI, this would prompt the user
848
+ return false; // Default to not confirmed for dangerous commands
849
+ }
850
+
851
+ return true;
852
+ }
853
+
854
+ private generateCommandExplanation(
855
+ command: string,
856
+ args: string[],
857
+ intentResult: IntentResult
858
+ ): string {
859
+ const fullCommand = `${command} ${args.join(' ')}`;
860
+
861
+ return (
862
+ `This command (${fullCommand}) will execute the "${intentResult.intent}" operation` +
863
+ (intentResult.parameters
864
+ ? ` with parameters: ${JSON.stringify(intentResult.parameters)}`
865
+ : '') +
866
+ `. ${intentResult.reasoning || 'No additional context provided.'}`
867
+ );
868
+ }
869
+
870
+ /**
871
+ * Cleanup resources
872
+ */
873
+ destroy(): void {
874
+ // Cancel all running commands
875
+ for (const [id, process] of this.runningCommands) {
876
+ process.kill('SIGTERM');
877
+ }
878
+
879
+ this.runningCommands.clear();
880
+ this.commandHistory = [];
881
+ this.removeAllListeners();
882
+ }
883
+ }
884
+
885
+ export default CommandMapper;