@wundr.io/cli 1.0.11 → 1.0.13

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 (180) hide show
  1. package/bin/wundr.js +8 -4
  2. package/dist/ai/ai-service.d.ts.map +1 -1
  3. package/dist/ai/ai-service.js.map +1 -1
  4. package/dist/ai/claude-client.js.map +1 -1
  5. package/dist/ai/conversation-manager.js.map +1 -1
  6. package/dist/commands/ai.d.ts.map +1 -1
  7. package/dist/commands/ai.js +179 -24
  8. package/dist/commands/ai.js.map +1 -1
  9. package/dist/commands/analyze-optimized.d.ts.map +1 -1
  10. package/dist/commands/analyze-optimized.js +15 -6
  11. package/dist/commands/analyze-optimized.js.map +1 -1
  12. package/dist/commands/batch.d.ts +22 -0
  13. package/dist/commands/batch.d.ts.map +1 -1
  14. package/dist/commands/batch.js +130 -14
  15. package/dist/commands/batch.js.map +1 -1
  16. package/dist/commands/chat.d.ts +1 -0
  17. package/dist/commands/chat.d.ts.map +1 -1
  18. package/dist/commands/chat.js +7 -3
  19. package/dist/commands/chat.js.map +1 -1
  20. package/dist/commands/claude-init.d.ts +1 -1
  21. package/dist/commands/claude-init.d.ts.map +1 -1
  22. package/dist/commands/claude-init.js +16 -16
  23. package/dist/commands/claude-init.js.map +1 -1
  24. package/dist/commands/claude-setup.d.ts +5 -5
  25. package/dist/commands/claude-setup.d.ts.map +1 -1
  26. package/dist/commands/claude-setup.js +65 -59
  27. package/dist/commands/claude-setup.js.map +1 -1
  28. package/dist/commands/computer-setup.d.ts +1 -0
  29. package/dist/commands/computer-setup.d.ts.map +1 -1
  30. package/dist/commands/computer-setup.js +35 -7
  31. package/dist/commands/computer-setup.js.map +1 -1
  32. package/dist/commands/dashboard.js.map +1 -1
  33. package/dist/commands/govern.js.map +1 -1
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +3 -3
  36. package/dist/commands/init.js.map +1 -1
  37. package/dist/commands/orchestrator.d.ts.map +1 -1
  38. package/dist/commands/orchestrator.js +11 -4
  39. package/dist/commands/orchestrator.js.map +1 -1
  40. package/dist/commands/performance-optimizer.d.ts.map +1 -1
  41. package/dist/commands/performance-optimizer.js.map +1 -1
  42. package/dist/commands/rag.d.ts.map +1 -1
  43. package/dist/commands/rag.js +9 -6
  44. package/dist/commands/rag.js.map +1 -1
  45. package/dist/commands/setup.d.ts +5 -10
  46. package/dist/commands/setup.d.ts.map +1 -1
  47. package/dist/commands/setup.js +35 -260
  48. package/dist/commands/setup.js.map +1 -1
  49. package/dist/commands/watch.d.ts.map +1 -1
  50. package/dist/commands/watch.js.map +1 -1
  51. package/dist/context/session-manager.js.map +1 -1
  52. package/dist/framework/command-interface.d.ts +349 -0
  53. package/dist/framework/command-interface.d.ts.map +1 -0
  54. package/dist/framework/command-interface.js +101 -0
  55. package/dist/framework/command-interface.js.map +1 -0
  56. package/dist/framework/command-registry.d.ts +173 -0
  57. package/dist/framework/command-registry.d.ts.map +1 -0
  58. package/dist/framework/command-registry.js +734 -0
  59. package/dist/framework/command-registry.js.map +1 -0
  60. package/dist/framework/completion-exporter.d.ts +79 -0
  61. package/dist/framework/completion-exporter.d.ts.map +1 -0
  62. package/dist/framework/completion-exporter.js +259 -0
  63. package/dist/framework/completion-exporter.js.map +1 -0
  64. package/dist/framework/debug-logger.d.ts +163 -0
  65. package/dist/framework/debug-logger.d.ts.map +1 -0
  66. package/dist/framework/debug-logger.js +373 -0
  67. package/dist/framework/debug-logger.js.map +1 -0
  68. package/dist/framework/error-handler.d.ts +196 -0
  69. package/dist/framework/error-handler.d.ts.map +1 -0
  70. package/dist/framework/error-handler.js +613 -0
  71. package/dist/framework/error-handler.js.map +1 -0
  72. package/dist/framework/help-generator.d.ts +78 -0
  73. package/dist/framework/help-generator.d.ts.map +1 -0
  74. package/dist/framework/help-generator.js +414 -0
  75. package/dist/framework/help-generator.js.map +1 -0
  76. package/dist/framework/index.d.ts +62 -0
  77. package/dist/framework/index.d.ts.map +1 -0
  78. package/dist/framework/index.js +95 -0
  79. package/dist/framework/index.js.map +1 -0
  80. package/dist/framework/interactive-repl.d.ts +138 -0
  81. package/dist/framework/interactive-repl.d.ts.map +1 -0
  82. package/dist/framework/interactive-repl.js +567 -0
  83. package/dist/framework/interactive-repl.js.map +1 -0
  84. package/dist/framework/output-formatter.d.ts +274 -0
  85. package/dist/framework/output-formatter.d.ts.map +1 -0
  86. package/dist/framework/output-formatter.js +545 -0
  87. package/dist/framework/output-formatter.js.map +1 -0
  88. package/dist/framework/progress-manager.d.ts +192 -0
  89. package/dist/framework/progress-manager.d.ts.map +1 -0
  90. package/dist/framework/progress-manager.js +408 -0
  91. package/dist/framework/progress-manager.js.map +1 -0
  92. package/dist/interactive/interactive-mode.js.map +1 -1
  93. package/dist/nlp/command-mapper.js.map +1 -1
  94. package/dist/nlp/command-parser.js.map +1 -1
  95. package/dist/nlp/intent-parser.d.ts.map +1 -1
  96. package/dist/nlp/intent-parser.js +4 -2
  97. package/dist/nlp/intent-parser.js.map +1 -1
  98. package/dist/plugins/plugin-manager.d.ts +2 -1
  99. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  100. package/dist/plugins/plugin-manager.js +30 -19
  101. package/dist/plugins/plugin-manager.js.map +1 -1
  102. package/dist/utils/backup-rollback-manager.d.ts.map +1 -1
  103. package/dist/utils/backup-rollback-manager.js +1 -2
  104. package/dist/utils/backup-rollback-manager.js.map +1 -1
  105. package/dist/utils/logger.js.map +1 -1
  106. package/package.json +6 -6
  107. package/src/ai/ai-service.ts +16 -17
  108. package/src/ai/claude-client.ts +16 -16
  109. package/src/ai/conversation-manager.ts +29 -29
  110. package/src/cli.ts +4 -4
  111. package/src/commands/ai.ts +246 -78
  112. package/src/commands/alignment.ts +74 -74
  113. package/src/commands/analyze-optimized.ts +111 -78
  114. package/src/commands/analyze.ts +14 -14
  115. package/src/commands/batch.ts +179 -42
  116. package/src/commands/chat.ts +37 -30
  117. package/src/commands/claude-init.ts +41 -45
  118. package/src/commands/claude-setup.ts +204 -119
  119. package/src/commands/computer-setup.ts +85 -43
  120. package/src/commands/create-command.ts +4 -4
  121. package/src/commands/create.ts +27 -27
  122. package/src/commands/dashboard.ts +24 -24
  123. package/src/commands/govern.ts +25 -25
  124. package/src/commands/governance.ts +34 -34
  125. package/src/commands/guardian.ts +56 -56
  126. package/src/commands/init.ts +25 -22
  127. package/src/commands/orchestrator.ts +68 -41
  128. package/src/commands/performance-optimizer.ts +34 -35
  129. package/src/commands/plugins.ts +27 -27
  130. package/src/commands/project-update.ts +175 -72
  131. package/src/commands/rag.ts +185 -78
  132. package/src/commands/session.ts +35 -35
  133. package/src/commands/setup.ts +40 -344
  134. package/src/commands/test-init.ts +3 -3
  135. package/src/commands/test.ts +4 -4
  136. package/src/commands/watch.ts +28 -29
  137. package/src/commands/worktree.ts +49 -49
  138. package/src/context/context-manager.ts +10 -10
  139. package/src/context/session-manager.ts +41 -41
  140. package/src/framework/command-interface.ts +520 -0
  141. package/src/framework/command-registry.ts +942 -0
  142. package/src/framework/completion-exporter.ts +383 -0
  143. package/src/framework/debug-logger.ts +519 -0
  144. package/src/framework/error-handler.ts +867 -0
  145. package/src/framework/help-generator.ts +540 -0
  146. package/src/framework/index.ts +169 -0
  147. package/src/framework/interactive-repl.ts +703 -0
  148. package/src/framework/output-formatter.ts +834 -0
  149. package/src/framework/progress-manager.ts +539 -0
  150. package/src/index.ts +4 -4
  151. package/src/interactive/interactive-mode.ts +16 -16
  152. package/src/lib/conflict-resolution.ts +799 -9
  153. package/src/lib/merge-strategy.ts +529 -7
  154. package/src/lib/safety-mechanisms.ts +422 -18
  155. package/src/lib/state-detection.ts +1015 -13
  156. package/src/nlp/command-mapper.ts +29 -29
  157. package/src/nlp/command-parser.ts +17 -17
  158. package/src/nlp/intent-classifier.ts +7 -7
  159. package/src/nlp/intent-parser.ts +54 -52
  160. package/src/plugins/plugin-manager.ts +61 -39
  161. package/src/tests/computer-setup-integration.test.ts +46 -15
  162. package/src/types/modules.d.ts +424 -1
  163. package/src/utils/backup-rollback-manager.ts +11 -8
  164. package/src/utils/config-manager.ts +3 -3
  165. package/src/utils/error-handler.ts +2 -2
  166. package/src/utils/logger.ts +22 -22
  167. package/templates/batch/ci-cd.yaml +7 -7
  168. package/test-suites/api/health.spec.ts +20 -23
  169. package/test-suites/helpers/test-config.ts +14 -13
  170. package/test-suites/ui/accessibility.spec.ts +27 -22
  171. package/test-suites/ui/smoke.spec.ts +26 -21
  172. package/dist/commands/computer-setup-commands.d.ts +0 -53
  173. package/dist/commands/computer-setup-commands.d.ts.map +0 -1
  174. package/dist/commands/computer-setup-commands.js +0 -705
  175. package/dist/commands/computer-setup-commands.js.map +0 -1
  176. package/dist/commands/vp.d.ts +0 -7
  177. package/dist/commands/vp.d.ts.map +0 -1
  178. package/dist/commands/vp.js +0 -571
  179. package/dist/commands/vp.js.map +0 -1
  180. package/src/commands/computer-setup-commands.ts +0 -872
@@ -22,7 +22,7 @@ export class BatchCommands {
22
22
  constructor(
23
23
  private program: Command,
24
24
  private configManager: ConfigManager,
25
- private pluginManager: PluginManager,
25
+ private pluginManager: PluginManager
26
26
  ) {
27
27
  this.registerCommands();
28
28
  }
@@ -41,7 +41,7 @@ export class BatchCommands {
41
41
  .option('--continue-on-error', 'continue execution on command failures')
42
42
  .option(
43
43
  '--vars <vars>',
44
- 'variables to pass to batch job (JSON or key=value)',
44
+ 'variables to pass to batch job (JSON or key=value)'
45
45
  )
46
46
  .option('--timeout <ms>', 'global timeout for batch job')
47
47
  .action(async (file, options) => {
@@ -111,7 +111,7 @@ export class BatchCommands {
111
111
  .option(
112
112
  '--format <format>',
113
113
  'export format (json, shell, dockerfile)',
114
- 'json',
114
+ 'json'
115
115
  )
116
116
  .option('--output <path>', 'output file path')
117
117
  .action(async (file, options) => {
@@ -124,7 +124,7 @@ export class BatchCommands {
124
124
  .description('import batch job from different formats')
125
125
  .option(
126
126
  '--format <format>',
127
- 'source format (json, shell, package-scripts)',
127
+ 'source format (json, shell, package-scripts)'
128
128
  )
129
129
  .option('--name <name>', 'batch job name')
130
130
  .action(async (file, options) => {
@@ -189,7 +189,7 @@ export class BatchCommands {
189
189
  'WUNDR_BATCH_RUN_FAILED',
190
190
  'Failed to run batch job',
191
191
  { file, options },
192
- true,
192
+ true
193
193
  );
194
194
  }
195
195
  }
@@ -215,7 +215,7 @@ export class BatchCommands {
215
215
  process.cwd(),
216
216
  '.wundr',
217
217
  'batch',
218
- `${name}.yaml`,
218
+ `${name}.yaml`
219
219
  );
220
220
  await fs.ensureDir(path.dirname(jobPath));
221
221
  await fs.writeFile(jobPath, YAML.stringify(job));
@@ -226,7 +226,7 @@ export class BatchCommands {
226
226
  'WUNDR_BATCH_CREATE_FAILED',
227
227
  'Failed to create batch job',
228
228
  { name, options },
229
- true,
229
+ true
230
230
  );
231
231
  }
232
232
  }
@@ -246,7 +246,7 @@ export class BatchCommands {
246
246
 
247
247
  const files = await fs.readdir(batchDir);
248
248
  const yamlFiles = files.filter(
249
- f => f.endsWith('.yaml') || f.endsWith('.yml'),
249
+ f => f.endsWith('.yaml') || f.endsWith('.yml')
250
250
  );
251
251
 
252
252
  if (yamlFiles.length === 0) {
@@ -288,7 +288,7 @@ export class BatchCommands {
288
288
  'WUNDR_BATCH_LIST_FAILED',
289
289
  'Failed to list batch jobs',
290
290
  { options },
291
- true,
291
+ true
292
292
  );
293
293
  }
294
294
  }
@@ -325,7 +325,7 @@ export class BatchCommands {
325
325
  'WUNDR_BATCH_VALIDATE_FAILED',
326
326
  'Failed to validate batch job',
327
327
  { file },
328
- true,
328
+ true
329
329
  );
330
330
  }
331
331
  }
@@ -352,7 +352,7 @@ export class BatchCommands {
352
352
  'WUNDR_BATCH_STOP_FAILED',
353
353
  'Failed to stop batch job',
354
354
  { jobId },
355
- true,
355
+ true
356
356
  );
357
357
  }
358
358
  }
@@ -387,7 +387,7 @@ export class BatchCommands {
387
387
  File: path.basename(job.file),
388
388
  Status: job.status,
389
389
  Duration: `${Date.now() - job.startTime}ms`,
390
- }),
390
+ })
391
391
  );
392
392
 
393
393
  console.table(jobData);
@@ -397,7 +397,7 @@ export class BatchCommands {
397
397
  'WUNDR_BATCH_STATUS_FAILED',
398
398
  'Failed to show job status',
399
399
  { jobId },
400
- true,
400
+ true
401
401
  );
402
402
  }
403
403
  }
@@ -431,7 +431,7 @@ export class BatchCommands {
431
431
  'WUNDR_BATCH_SCHEDULE_FAILED',
432
432
  'Failed to schedule batch job',
433
433
  { file, options },
434
- true,
434
+ true
435
435
  );
436
436
  }
437
437
  }
@@ -471,7 +471,7 @@ export class BatchCommands {
471
471
  'WUNDR_BATCH_EXPORT_FAILED',
472
472
  'Failed to export batch job',
473
473
  { file, options },
474
- true,
474
+ true
475
475
  );
476
476
  }
477
477
  }
@@ -504,7 +504,7 @@ export class BatchCommands {
504
504
  process.cwd(),
505
505
  '.wundr',
506
506
  'batch',
507
- `${jobName}.yaml`,
507
+ `${jobName}.yaml`
508
508
  );
509
509
 
510
510
  await fs.ensureDir(path.dirname(jobPath));
@@ -516,7 +516,7 @@ export class BatchCommands {
516
516
  'WUNDR_BATCH_IMPORT_FAILED',
517
517
  'Failed to import batch job',
518
518
  { file, options },
519
- true,
519
+ true
520
520
  );
521
521
  }
522
522
  }
@@ -542,7 +542,7 @@ export class BatchCommands {
542
542
  }
543
543
 
544
544
  private async validateJobStructure(
545
- job: BatchJob,
545
+ job: BatchJob
546
546
  ): Promise<{ valid: boolean; errors: string[] }> {
547
547
  const errors: string[] = [];
548
548
 
@@ -563,9 +563,14 @@ export class BatchCommands {
563
563
  return { valid: errors.length === 0, errors };
564
564
  }
565
565
 
566
+ /**
567
+ * Regex for valid variable names in batch templates.
568
+ */
569
+ private static readonly VALID_VARIABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
570
+
566
571
  private async processJobVariables(
567
572
  job: BatchJob,
568
- vars?: string,
573
+ vars?: string
569
574
  ): Promise<BatchJob> {
570
575
  let variables: Record<string, any> = {};
571
576
 
@@ -576,25 +581,58 @@ export class BatchCommands {
576
581
  } catch {
577
582
  // Parse as key=value pairs
578
583
  vars.split(',').forEach(pair => {
579
- const [key, value] = pair.split('=');
580
- if (key && value) {
581
- variables[key.trim()] = value.trim();
584
+ const eqIndex = pair.indexOf('=');
585
+ if (eqIndex > 0) {
586
+ const key = pair.slice(0, eqIndex).trim();
587
+ const value = pair.slice(eqIndex + 1).trim();
588
+ if (key) {
589
+ variables[key] = value;
590
+ }
582
591
  }
583
592
  });
584
593
  }
585
594
  }
586
595
 
587
- // Replace variables in job
588
- const processedJob = JSON.parse(JSON.stringify(job));
589
- const jobString = JSON.stringify(processedJob);
590
- let processedString = jobString;
596
+ // Validate variable names to prevent regex injection
597
+ for (const key of Object.keys(variables)) {
598
+ if (!BatchCommands.VALID_VARIABLE_NAME.test(key)) {
599
+ throw new Error(
600
+ `Invalid variable name "${key}": must match [a-zA-Z_][a-zA-Z0-9_]*`
601
+ );
602
+ }
603
+ }
604
+
605
+ // SECURITY: Instead of replacing {{var}} in a JSON-stringified string
606
+ // (which allows JSON structure breakout via quotes in values), we walk
607
+ // the job structure and only replace in string leaf values. Values are
608
+ // JSON-escaped to prevent structure injection.
609
+ const processedJob: BatchJob = JSON.parse(JSON.stringify(job));
610
+
611
+ const replaceVarsInString = (str: string): string => {
612
+ let result = str;
613
+ for (const [key, value] of Object.entries(variables)) {
614
+ const placeholder = `{{${key}}}`;
615
+ // Only replace exact placeholder matches, not regex patterns
616
+ while (result.includes(placeholder)) {
617
+ result = result.replace(placeholder, String(value));
618
+ }
619
+ }
620
+ return result;
621
+ };
591
622
 
592
- Object.entries(variables).forEach(([key, value]) => {
593
- const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
594
- processedString = processedString.replace(placeholder, String(value));
595
- });
623
+ // Walk commands and replace variables in string fields only
624
+ processedJob.commands = processedJob.commands.map(cmd => ({
625
+ ...cmd,
626
+ command: replaceVarsInString(cmd.command),
627
+ args: cmd.args?.map(arg => replaceVarsInString(arg)),
628
+ condition: cmd.condition ? replaceVarsInString(cmd.condition) : undefined,
629
+ }));
596
630
 
597
- return JSON.parse(processedString);
631
+ if (processedJob.description) {
632
+ processedJob.description = replaceVarsInString(processedJob.description);
633
+ }
634
+
635
+ return processedJob;
598
636
  }
599
637
 
600
638
  private async showDryRun(job: BatchJob): Promise<void> {
@@ -627,7 +665,7 @@ export class BatchCommands {
627
665
  private async executeBatchJob(
628
666
  job: BatchJob,
629
667
  _jobId: string,
630
- _options: any,
668
+ _options: any
631
669
  ): Promise<void> {
632
670
  const tasks = job.commands.map((cmd, _index) => ({
633
671
  title: cmd.command,
@@ -649,9 +687,94 @@ export class BatchCommands {
649
687
  await listr.run();
650
688
  }
651
689
 
690
+ /**
691
+ * Shell metacharacters that indicate injection attempts.
692
+ * These characters have special meaning in sh/bash and must not
693
+ * appear in arguments passed to spawn().
694
+ */
695
+ private static readonly SHELL_METACHARACTERS = /[;&|`$(){}[\]<>!#~*?\n\r\\]/;
696
+
697
+ /**
698
+ * Tokenize a command string into [binary, ...args] without shell
699
+ * interpretation. Supports simple single/double quoting.
700
+ */
701
+ private tokenizeCommand(command: string): string[] {
702
+ const tokens: string[] = [];
703
+ let current = '';
704
+ let inSingle = false;
705
+ let inDouble = false;
706
+
707
+ for (let i = 0; i < command.length; i++) {
708
+ const ch = command[i];
709
+
710
+ if (inSingle) {
711
+ if (ch === "'") {
712
+ inSingle = false;
713
+ } else {
714
+ current += ch;
715
+ }
716
+ continue;
717
+ }
718
+
719
+ if (inDouble) {
720
+ if (ch === '"') {
721
+ inDouble = false;
722
+ } else {
723
+ current += ch;
724
+ }
725
+ continue;
726
+ }
727
+
728
+ if (ch === "'") {
729
+ inSingle = true;
730
+ continue;
731
+ }
732
+ if (ch === '"') {
733
+ inDouble = true;
734
+ continue;
735
+ }
736
+
737
+ if (ch === ' ' || ch === '\t') {
738
+ if (current.length > 0) {
739
+ tokens.push(current);
740
+ current = '';
741
+ }
742
+ continue;
743
+ }
744
+
745
+ current += ch;
746
+ }
747
+
748
+ if (current.length > 0) {
749
+ tokens.push(current);
750
+ }
751
+
752
+ if (inSingle || inDouble) {
753
+ throw new Error(`Unterminated quote in command: ${command}`);
754
+ }
755
+
756
+ return tokens;
757
+ }
758
+
759
+ /**
760
+ * Validate that no argument contains shell metacharacters.
761
+ * This is a defense-in-depth measure: since we never use shell: true,
762
+ * metacharacters would be treated literally, but their presence strongly
763
+ * suggests a command injection attempt.
764
+ */
765
+ private validateArgs(args: string[]): void {
766
+ for (const arg of args) {
767
+ if (BatchCommands.SHELL_METACHARACTERS.test(arg)) {
768
+ throw new Error(
769
+ `Argument contains shell metacharacters (possible injection): "${arg}"`
770
+ );
771
+ }
772
+ }
773
+ }
774
+
652
775
  private async executeCommand(
653
776
  cmd: BatchCommand,
654
- _options: any,
777
+ _options: any
655
778
  ): Promise<void> {
656
779
  // Check condition if specified
657
780
  if (cmd.condition && !(await this.evaluateCondition(cmd.condition))) {
@@ -660,13 +783,27 @@ export class BatchCommands {
660
783
  }
661
784
 
662
785
  const { spawn } = await import('child_process');
663
- const [command, ...args] = cmd.command.split(' ');
664
- const finalArgs = cmd.args ? [...args, ...cmd.args] : args;
786
+
787
+ // Tokenize the command string into binary + args without shell
788
+ // interpretation. This prevents command injection via shell metacharacters
789
+ // like ; | & ` $() etc.
790
+ const tokens = this.tokenizeCommand(cmd.command);
791
+ if (tokens.length === 0) {
792
+ throw new Error('Empty command');
793
+ }
794
+
795
+ const [command, ...parsedArgs] = tokens;
796
+ const finalArgs = cmd.args ? [...parsedArgs, ...cmd.args] : parsedArgs;
797
+
798
+ // Validate arguments for shell metacharacters as defense-in-depth
799
+ this.validateArgs(finalArgs);
665
800
 
666
801
  return new Promise((resolve, reject) => {
802
+ // SECURITY: No shell: true. The command is executed directly via
803
+ // execvp(), so shell metacharacters in arguments are treated as
804
+ // literal characters rather than being interpreted by a shell.
667
805
  const child = spawn(command ?? 'echo', finalArgs, {
668
806
  stdio: ['ignore', 'pipe', 'pipe'],
669
- shell: true,
670
807
  });
671
808
 
672
809
  let output = '';
@@ -801,13 +938,13 @@ export class BatchCommands {
801
938
 
802
939
  private async createJobFromTemplate(
803
940
  name: string,
804
- template: string,
941
+ template: string
805
942
  ): Promise<BatchJob> {
806
943
  // Load template and create job
807
944
  const templatePath = path.join(
808
945
  __dirname,
809
946
  '../../templates/batch',
810
- `${template}.yaml`,
947
+ `${template}.yaml`
811
948
  );
812
949
  if (await fs.pathExists(templatePath)) {
813
950
  const templateJob = await this.loadBatchJob(templatePath);
@@ -860,7 +997,7 @@ export class BatchCommands {
860
997
 
861
998
  private async importFromShell(
862
999
  file: string,
863
- name?: string,
1000
+ name?: string
864
1001
  ): Promise<BatchJob> {
865
1002
  const content = await fs.readFile(file, 'utf8');
866
1003
  const commands = content
@@ -877,7 +1014,7 @@ export class BatchCommands {
877
1014
 
878
1015
  private async importFromPackageScripts(
879
1016
  file: string,
880
- name?: string,
1017
+ name?: string
881
1018
  ): Promise<BatchJob> {
882
1019
  const packageJson = await fs.readJson(file);
883
1020
  const scripts = packageJson.scripts || {};
@@ -900,7 +1037,7 @@ export class BatchCommands {
900
1037
  if (await fs.pathExists(templatesDir)) {
901
1038
  const templates = await fs.readdir(templatesDir);
902
1039
  const yamlTemplates = templates.filter(
903
- t => t.endsWith('.yaml') || t.endsWith('.yml'),
1040
+ t => t.endsWith('.yaml') || t.endsWith('.yml')
904
1041
  );
905
1042
 
906
1043
  if (yamlTemplates.length > 0) {
@@ -924,7 +1061,7 @@ export class BatchCommands {
924
1061
  const templatePath = path.join(
925
1062
  __dirname,
926
1063
  '../../templates/batch',
927
- `${name}.yaml`,
1064
+ `${name}.yaml`
928
1065
  );
929
1066
 
930
1067
  await fs.ensureDir(path.dirname(templatePath));
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import fs from 'fs-extra';
5
5
  import inquirer from 'inquirer';
6
6
 
7
+ import { AIService } from '../ai/ai-service';
7
8
  import { errorHandler } from '../utils/error-handler';
8
9
  import { logger } from '../utils/logger';
9
10
 
@@ -17,12 +18,14 @@ import type { Command } from 'commander';
17
18
  */
18
19
  export class ChatCommands {
19
20
  private activeSessions: Map<string, ChatSession> = new Map();
21
+ private aiService: AIService;
20
22
 
21
23
  constructor(
22
24
  private program: Command,
23
25
  private configManager: ConfigManager,
24
- private pluginManager: PluginManager,
26
+ private pluginManager: PluginManager
25
27
  ) {
28
+ this.aiService = new AIService(configManager);
26
29
  this.registerCommands();
27
30
  }
28
31
 
@@ -41,7 +44,7 @@ export class ChatCommands {
41
44
  .option(
42
45
  '--persona <persona>',
43
46
  'AI persona (developer, architect, reviewer)',
44
- 'developer',
47
+ 'developer'
45
48
  )
46
49
  .option('--session-name <name>', 'custom session name')
47
50
  .action(async options => {
@@ -84,7 +87,7 @@ export class ChatCommands {
84
87
  .option(
85
88
  '--format <format>',
86
89
  'export format (json, markdown, txt)',
87
- 'markdown',
90
+ 'markdown'
88
91
  )
89
92
  .option('--output <path>', 'output file path')
90
93
  .action(async (sessionId, options) => {
@@ -133,7 +136,7 @@ export class ChatCommands {
133
136
  .option(
134
137
  '--action <action>',
135
138
  'action to perform (explain, review, improve)',
136
- 'explain',
139
+ 'explain'
137
140
  )
138
141
  .option('--model <model>', 'AI model to use')
139
142
  .action(async (file, options) => {
@@ -148,7 +151,7 @@ export class ChatCommands {
148
151
  .option(
149
152
  '--action <action>',
150
153
  'action to perform (explain, review, improve)',
151
- 'explain',
154
+ 'explain'
152
155
  )
153
156
  .action(async options => {
154
157
  await this.chatWithCode(options);
@@ -189,7 +192,7 @@ export class ChatCommands {
189
192
  console.log(chalk.gray(`Context: ${session.context}`));
190
193
  }
191
194
  console.log(
192
- chalk.gray('Type "exit" to end the session, "help" for commands\n'),
195
+ chalk.gray('Type "exit" to end the session, "help" for commands\n')
193
196
  );
194
197
 
195
198
  await this.runChatLoop(session);
@@ -198,7 +201,7 @@ export class ChatCommands {
198
201
  'WUNDR_CHAT_START_FAILED',
199
202
  'Failed to start chat session',
200
203
  { options },
201
- true,
204
+ true
202
205
  );
203
206
  }
204
207
  }
@@ -221,7 +224,7 @@ export class ChatCommands {
221
224
  console.log(chalk.gray(`Session ID: ${session.id}`));
222
225
  console.log(chalk.gray(`Messages: ${session.history.length}`));
223
226
  console.log(
224
- chalk.gray(`Last updated: ${session.updated.toLocaleString()}\n`),
227
+ chalk.gray(`Last updated: ${session.updated.toLocaleString()}\n`)
225
228
  );
226
229
 
227
230
  // Show recent messages
@@ -244,7 +247,7 @@ export class ChatCommands {
244
247
  'WUNDR_CHAT_RESUME_FAILED',
245
248
  'Failed to resume chat session',
246
249
  { sessionId },
247
- true,
250
+ true
248
251
  );
249
252
  }
250
253
  }
@@ -281,7 +284,7 @@ export class ChatCommands {
281
284
  'WUNDR_CHAT_LIST_FAILED',
282
285
  'Failed to list chat sessions',
283
286
  { options },
284
- true,
287
+ true
285
288
  );
286
289
  }
287
290
  }
@@ -291,7 +294,7 @@ export class ChatCommands {
291
294
  */
292
295
  private async askSingleQuestion(
293
296
  message: string,
294
- options: any,
297
+ options: any
295
298
  ): Promise<void> {
296
299
  try {
297
300
  logger.debug('Processing single question...');
@@ -326,7 +329,7 @@ export class ChatCommands {
326
329
  'WUNDR_CHAT_ASK_FAILED',
327
330
  'Failed to process question',
328
331
  { message, options },
329
- true,
332
+ true
330
333
  );
331
334
  }
332
335
  }
@@ -336,7 +339,7 @@ export class ChatCommands {
336
339
  */
337
340
  private async exportChatSession(
338
341
  sessionId: string,
339
- options: any,
342
+ options: any
340
343
  ): Promise<void> {
341
344
  try {
342
345
  logger.info(`Exporting chat session: ${sessionId}`);
@@ -372,7 +375,7 @@ export class ChatCommands {
372
375
  'WUNDR_CHAT_EXPORT_FAILED',
373
376
  'Failed to export chat session',
374
377
  { sessionId, options },
375
- true,
378
+ true
376
379
  );
377
380
  }
378
381
  }
@@ -413,7 +416,7 @@ export class ChatCommands {
413
416
  'WUNDR_CHAT_IMPORT_FAILED',
414
417
  'Failed to import chat session',
415
418
  { file, options },
416
- true,
419
+ true
417
420
  );
418
421
  }
419
422
  }
@@ -423,7 +426,7 @@ export class ChatCommands {
423
426
  */
424
427
  private async deleteChatSession(
425
428
  sessionId: string,
426
- options: any,
429
+ options: any
427
430
  ): Promise<void> {
428
431
  try {
429
432
  const session = await this.loadChatSession(sessionId);
@@ -456,7 +459,7 @@ export class ChatCommands {
456
459
  'WUNDR_CHAT_DELETE_FAILED',
457
460
  'Failed to delete chat session',
458
461
  { sessionId, options },
459
- true,
462
+ true
460
463
  );
461
464
  }
462
465
  }
@@ -490,7 +493,7 @@ export class ChatCommands {
490
493
  'WUNDR_CHAT_FILE_FAILED',
491
494
  'Failed to chat with file',
492
495
  { file, options },
493
- true,
496
+ true
494
497
  );
495
498
  }
496
499
  }
@@ -533,7 +536,7 @@ export class ChatCommands {
533
536
  'WUNDR_CHAT_CODE_FAILED',
534
537
  'Failed to chat with code',
535
538
  { options },
536
- true,
539
+ true
537
540
  );
538
541
  }
539
542
  }
@@ -585,7 +588,7 @@ export class ChatCommands {
585
588
  } catch (error) {
586
589
  logger.error('Chat error:', error);
587
590
  console.log(
588
- chalk.red('Sorry, there was an error processing your message.\n'),
591
+ chalk.red('Sorry, there was an error processing your message.\n')
589
592
  );
590
593
  }
591
594
  }
@@ -598,7 +601,7 @@ export class ChatCommands {
598
601
 
599
602
  private async sendMessage(
600
603
  session: ChatSession,
601
- message: string,
604
+ message: string
602
605
  ): Promise<string> {
603
606
  // Add user message to history
604
607
  const userMessage: ChatMessage = {
@@ -625,9 +628,13 @@ export class ChatCommands {
625
628
  }
626
629
 
627
630
  private async callAI(session: ChatSession, message: string): Promise<string> {
628
- // Mock AI service call
629
- // In a real implementation, this would call Claude, GPT, etc.
630
- return `This is a mock response to: "${message}". The AI would provide a helpful response here based on the context and conversation history.`;
631
+ if (!this.aiService.isReady()) {
632
+ throw new Error(
633
+ 'AI service not configured. Set ANTHROPIC_API_KEY environment variable to enable chat.'
634
+ );
635
+ }
636
+
637
+ return this.aiService.sendMessage(session.id, message);
631
638
  }
632
639
 
633
640
  private showChatHelp(): void {
@@ -644,7 +651,7 @@ export class ChatCommands {
644
651
 
645
652
  private async handleChatCommand(
646
653
  session: ChatSession,
647
- command: string,
654
+ command: string
648
655
  ): Promise<void> {
649
656
  const [cmd, ...args] = command.slice(1).split(' ');
650
657
 
@@ -693,20 +700,20 @@ export class ChatCommands {
693
700
  process.cwd(),
694
701
  '.wundr',
695
702
  'chat',
696
- `${session.id}.json`,
703
+ `${session.id}.json`
697
704
  );
698
705
  await fs.ensureDir(path.dirname(sessionPath));
699
706
  await fs.writeJson(sessionPath, session, { spaces: 2 });
700
707
  }
701
708
 
702
709
  private async loadChatSession(
703
- sessionId: string,
710
+ sessionId: string
704
711
  ): Promise<ChatSession | null> {
705
712
  const sessionPath = path.join(
706
713
  process.cwd(),
707
714
  '.wundr',
708
715
  'chat',
709
- `${sessionId}.json`,
716
+ `${sessionId}.json`
710
717
  );
711
718
  if (await fs.pathExists(sessionPath)) {
712
719
  const data = await fs.readJson(sessionPath);
@@ -751,7 +758,7 @@ export class ChatCommands {
751
758
  process.cwd(),
752
759
  '.wundr',
753
760
  'chat',
754
- `${sessionId}.json`,
761
+ `${sessionId}.json`
755
762
  );
756
763
  if (await fs.pathExists(sessionPath)) {
757
764
  await fs.remove(sessionPath);
@@ -869,7 +876,7 @@ export class ChatCommands {
869
876
  Object.entries(variables).forEach(([key, value]) => {
870
877
  processedTemplate = processedTemplate.replace(
871
878
  `{{${key}}}`,
872
- String(value),
879
+ String(value)
873
880
  );
874
881
  });
875
882
  }