@wundr.io/cli 1.0.12 → 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 (234) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ai/ai-service.d.ts +152 -0
  3. package/dist/ai/ai-service.d.ts.map +1 -0
  4. package/dist/ai/ai-service.js +430 -0
  5. package/dist/ai/ai-service.js.map +1 -0
  6. package/dist/ai/claude-client.d.ts +130 -0
  7. package/dist/ai/claude-client.d.ts.map +1 -0
  8. package/dist/ai/claude-client.js +340 -0
  9. package/dist/ai/claude-client.js.map +1 -0
  10. package/dist/ai/conversation-manager.d.ts +164 -0
  11. package/dist/ai/conversation-manager.d.ts.map +1 -0
  12. package/dist/ai/conversation-manager.js +614 -0
  13. package/dist/ai/conversation-manager.js.map +1 -0
  14. package/dist/ai/index.d.ts +5 -0
  15. package/dist/ai/index.d.ts.map +1 -0
  16. package/dist/ai/index.js +8 -0
  17. package/dist/ai/index.js.map +1 -0
  18. package/dist/cli.d.ts +36 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +192 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/commands/ai.d.ts +89 -0
  23. package/dist/commands/ai.d.ts.map +1 -0
  24. package/dist/commands/ai.js +954 -0
  25. package/dist/commands/ai.js.map +1 -0
  26. package/dist/commands/alignment.d.ts +78 -0
  27. package/dist/commands/alignment.d.ts.map +1 -0
  28. package/dist/commands/alignment.js +817 -0
  29. package/dist/commands/alignment.js.map +1 -0
  30. package/dist/commands/analyze-optimized.d.ts +14 -0
  31. package/dist/commands/analyze-optimized.d.ts.map +1 -0
  32. package/dist/commands/analyze-optimized.js +609 -0
  33. package/dist/commands/analyze-optimized.js.map +1 -0
  34. package/dist/commands/analyze.d.ts +65 -0
  35. package/dist/commands/analyze.d.ts.map +1 -0
  36. package/dist/commands/analyze.js +435 -0
  37. package/dist/commands/analyze.js.map +1 -0
  38. package/dist/commands/batch.d.ts +93 -0
  39. package/dist/commands/batch.d.ts.map +1 -0
  40. package/dist/commands/batch.js +854 -0
  41. package/dist/commands/batch.js.map +1 -0
  42. package/dist/commands/chat.d.ts +72 -0
  43. package/dist/commands/chat.d.ts.map +1 -0
  44. package/dist/commands/chat.js +678 -0
  45. package/dist/commands/chat.js.map +1 -0
  46. package/dist/commands/claude-init.d.ts +28 -0
  47. package/dist/commands/claude-init.d.ts.map +1 -0
  48. package/dist/commands/claude-init.js +591 -0
  49. package/dist/commands/claude-init.js.map +1 -0
  50. package/dist/commands/claude-setup.d.ts +119 -0
  51. package/dist/commands/claude-setup.d.ts.map +1 -0
  52. package/dist/commands/claude-setup.js +1079 -0
  53. package/dist/commands/claude-setup.js.map +1 -0
  54. package/dist/commands/computer-setup.d.ts +8 -0
  55. package/dist/commands/computer-setup.d.ts.map +1 -0
  56. package/dist/commands/computer-setup.js +877 -0
  57. package/dist/commands/computer-setup.js.map +1 -0
  58. package/dist/commands/create-command.d.ts +7 -0
  59. package/dist/commands/create-command.d.ts.map +1 -0
  60. package/dist/commands/create-command.js +158 -0
  61. package/dist/commands/create-command.js.map +1 -0
  62. package/dist/commands/create.d.ts +74 -0
  63. package/dist/commands/create.d.ts.map +1 -0
  64. package/dist/commands/create.js +556 -0
  65. package/dist/commands/create.js.map +1 -0
  66. package/dist/commands/dashboard.d.ts +91 -0
  67. package/dist/commands/dashboard.d.ts.map +1 -0
  68. package/dist/commands/dashboard.js +538 -0
  69. package/dist/commands/dashboard.js.map +1 -0
  70. package/dist/commands/govern.d.ts +70 -0
  71. package/dist/commands/govern.d.ts.map +1 -0
  72. package/dist/commands/govern.js +481 -0
  73. package/dist/commands/govern.js.map +1 -0
  74. package/dist/commands/governance.d.ts +17 -0
  75. package/dist/commands/governance.d.ts.map +1 -0
  76. package/dist/commands/governance.js +703 -0
  77. package/dist/commands/governance.js.map +1 -0
  78. package/dist/commands/guardian.d.ts +20 -0
  79. package/dist/commands/guardian.d.ts.map +1 -0
  80. package/dist/commands/guardian.js +597 -0
  81. package/dist/commands/guardian.js.map +1 -0
  82. package/dist/commands/init.d.ts +59 -0
  83. package/dist/commands/init.d.ts.map +1 -0
  84. package/dist/commands/init.js +650 -0
  85. package/dist/commands/init.js.map +1 -0
  86. package/dist/commands/orchestrator.d.ts +7 -0
  87. package/dist/commands/orchestrator.d.ts.map +1 -0
  88. package/dist/commands/orchestrator.js +578 -0
  89. package/dist/commands/orchestrator.js.map +1 -0
  90. package/dist/commands/performance-optimizer.d.ts +30 -0
  91. package/dist/commands/performance-optimizer.d.ts.map +1 -0
  92. package/dist/commands/performance-optimizer.js +650 -0
  93. package/dist/commands/performance-optimizer.js.map +1 -0
  94. package/dist/commands/plugins.d.ts +87 -0
  95. package/dist/commands/plugins.d.ts.map +1 -0
  96. package/dist/commands/plugins.js +685 -0
  97. package/dist/commands/plugins.js.map +1 -0
  98. package/dist/commands/rag.d.ts +7 -0
  99. package/dist/commands/rag.d.ts.map +1 -0
  100. package/dist/commands/rag.js +751 -0
  101. package/dist/commands/rag.js.map +1 -0
  102. package/dist/commands/session.d.ts +41 -0
  103. package/dist/commands/session.d.ts.map +1 -0
  104. package/dist/commands/session.js +441 -0
  105. package/dist/commands/session.js.map +1 -0
  106. package/dist/commands/setup.d.ts +24 -0
  107. package/dist/commands/setup.d.ts.map +1 -0
  108. package/dist/commands/setup.js +172 -0
  109. package/dist/commands/setup.js.map +1 -0
  110. package/dist/commands/test-init.d.ts +9 -0
  111. package/dist/commands/test-init.d.ts.map +1 -0
  112. package/dist/commands/test-init.js +222 -0
  113. package/dist/commands/test-init.js.map +1 -0
  114. package/dist/commands/test.d.ts +25 -0
  115. package/dist/commands/test.d.ts.map +1 -0
  116. package/dist/commands/test.js +217 -0
  117. package/dist/commands/test.js.map +1 -0
  118. package/dist/commands/watch.d.ts +76 -0
  119. package/dist/commands/watch.d.ts.map +1 -0
  120. package/dist/commands/watch.js +613 -0
  121. package/dist/commands/watch.js.map +1 -0
  122. package/dist/commands/worktree.d.ts +63 -0
  123. package/dist/commands/worktree.d.ts.map +1 -0
  124. package/dist/commands/worktree.js +774 -0
  125. package/dist/commands/worktree.js.map +1 -0
  126. package/dist/context/context-manager.d.ts +155 -0
  127. package/dist/context/context-manager.d.ts.map +1 -0
  128. package/dist/context/context-manager.js +383 -0
  129. package/dist/context/context-manager.js.map +1 -0
  130. package/dist/context/index.d.ts +3 -0
  131. package/dist/context/index.d.ts.map +1 -0
  132. package/dist/context/index.js +6 -0
  133. package/dist/context/index.js.map +1 -0
  134. package/dist/context/session-manager.d.ts +207 -0
  135. package/dist/context/session-manager.d.ts.map +1 -0
  136. package/dist/context/session-manager.js +686 -0
  137. package/dist/context/session-manager.js.map +1 -0
  138. package/dist/framework/command-interface.d.ts +349 -0
  139. package/dist/framework/command-interface.d.ts.map +1 -0
  140. package/dist/framework/command-interface.js +101 -0
  141. package/dist/framework/command-interface.js.map +1 -0
  142. package/dist/framework/command-registry.d.ts +173 -0
  143. package/dist/framework/command-registry.d.ts.map +1 -0
  144. package/dist/framework/command-registry.js +734 -0
  145. package/dist/framework/command-registry.js.map +1 -0
  146. package/dist/framework/completion-exporter.d.ts +79 -0
  147. package/dist/framework/completion-exporter.d.ts.map +1 -0
  148. package/dist/framework/completion-exporter.js +259 -0
  149. package/dist/framework/completion-exporter.js.map +1 -0
  150. package/dist/framework/debug-logger.d.ts +163 -0
  151. package/dist/framework/debug-logger.d.ts.map +1 -0
  152. package/dist/framework/debug-logger.js +373 -0
  153. package/dist/framework/debug-logger.js.map +1 -0
  154. package/dist/framework/error-handler.d.ts +196 -0
  155. package/dist/framework/error-handler.d.ts.map +1 -0
  156. package/dist/framework/error-handler.js +613 -0
  157. package/dist/framework/error-handler.js.map +1 -0
  158. package/dist/framework/help-generator.d.ts +78 -0
  159. package/dist/framework/help-generator.d.ts.map +1 -0
  160. package/dist/framework/help-generator.js +414 -0
  161. package/dist/framework/help-generator.js.map +1 -0
  162. package/dist/framework/index.d.ts +62 -0
  163. package/dist/framework/index.d.ts.map +1 -0
  164. package/dist/framework/index.js +95 -0
  165. package/dist/framework/index.js.map +1 -0
  166. package/dist/framework/interactive-repl.d.ts +138 -0
  167. package/dist/framework/interactive-repl.d.ts.map +1 -0
  168. package/dist/framework/interactive-repl.js +567 -0
  169. package/dist/framework/interactive-repl.js.map +1 -0
  170. package/dist/framework/output-formatter.d.ts +274 -0
  171. package/dist/framework/output-formatter.d.ts.map +1 -0
  172. package/dist/framework/output-formatter.js +545 -0
  173. package/dist/framework/output-formatter.js.map +1 -0
  174. package/dist/framework/progress-manager.d.ts +192 -0
  175. package/dist/framework/progress-manager.d.ts.map +1 -0
  176. package/dist/framework/progress-manager.js +408 -0
  177. package/dist/framework/progress-manager.js.map +1 -0
  178. package/dist/index.d.ts +8 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +51 -0
  181. package/dist/index.js.map +1 -0
  182. package/dist/interactive/interactive-mode.d.ts +76 -0
  183. package/dist/interactive/interactive-mode.d.ts.map +1 -0
  184. package/dist/interactive/interactive-mode.js +732 -0
  185. package/dist/interactive/interactive-mode.js.map +1 -0
  186. package/dist/nlp/command-mapper.d.ts +174 -0
  187. package/dist/nlp/command-mapper.d.ts.map +1 -0
  188. package/dist/nlp/command-mapper.js +624 -0
  189. package/dist/nlp/command-mapper.js.map +1 -0
  190. package/dist/nlp/command-parser.d.ts +106 -0
  191. package/dist/nlp/command-parser.d.ts.map +1 -0
  192. package/dist/nlp/command-parser.js +417 -0
  193. package/dist/nlp/command-parser.js.map +1 -0
  194. package/dist/nlp/index.d.ts +5 -0
  195. package/dist/nlp/index.d.ts.map +1 -0
  196. package/dist/nlp/index.js +8 -0
  197. package/dist/nlp/index.js.map +1 -0
  198. package/dist/nlp/intent-classifier.d.ts +59 -0
  199. package/dist/nlp/intent-classifier.d.ts.map +1 -0
  200. package/dist/nlp/intent-classifier.js +384 -0
  201. package/dist/nlp/intent-classifier.js.map +1 -0
  202. package/dist/nlp/intent-parser.d.ts +152 -0
  203. package/dist/nlp/intent-parser.d.ts.map +1 -0
  204. package/dist/nlp/intent-parser.js +746 -0
  205. package/dist/nlp/intent-parser.js.map +1 -0
  206. package/dist/plugins/plugin-manager.d.ts +121 -0
  207. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  208. package/dist/plugins/plugin-manager.js +606 -0
  209. package/dist/plugins/plugin-manager.js.map +1 -0
  210. package/dist/types/index.d.ts +224 -0
  211. package/dist/types/index.d.ts.map +1 -0
  212. package/dist/types/index.js +3 -0
  213. package/dist/types/index.js.map +1 -0
  214. package/dist/utils/backup-rollback-manager.d.ts +72 -0
  215. package/dist/utils/backup-rollback-manager.d.ts.map +1 -0
  216. package/dist/utils/backup-rollback-manager.js +288 -0
  217. package/dist/utils/backup-rollback-manager.js.map +1 -0
  218. package/dist/utils/claude-config-installer.d.ts +98 -0
  219. package/dist/utils/claude-config-installer.d.ts.map +1 -0
  220. package/dist/utils/claude-config-installer.js +678 -0
  221. package/dist/utils/claude-config-installer.js.map +1 -0
  222. package/dist/utils/config-manager.d.ts +73 -0
  223. package/dist/utils/config-manager.d.ts.map +1 -0
  224. package/dist/utils/config-manager.js +339 -0
  225. package/dist/utils/config-manager.js.map +1 -0
  226. package/dist/utils/error-handler.d.ts +46 -0
  227. package/dist/utils/error-handler.d.ts.map +1 -0
  228. package/dist/utils/error-handler.js +169 -0
  229. package/dist/utils/error-handler.js.map +1 -0
  230. package/dist/utils/logger.d.ts +25 -0
  231. package/dist/utils/logger.d.ts.map +1 -0
  232. package/dist/utils/logger.js +105 -0
  233. package/dist/utils/logger.js.map +1 -0
  234. package/package.json +23 -23
@@ -0,0 +1,854 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BatchCommands = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
8
+ const listr2_1 = require("listr2");
9
+ const yaml_1 = tslib_1.__importDefault(require("yaml"));
10
+ const error_handler_1 = require("../utils/error-handler");
11
+ const logger_1 = require("../utils/logger");
12
+ /**
13
+ * Batch commands for YAML automation and batch processing
14
+ */
15
+ class BatchCommands {
16
+ program;
17
+ configManager;
18
+ pluginManager;
19
+ runningJobs = new Map();
20
+ constructor(program, configManager, pluginManager) {
21
+ this.program = program;
22
+ this.configManager = configManager;
23
+ this.pluginManager = pluginManager;
24
+ this.registerCommands();
25
+ }
26
+ registerCommands() {
27
+ const batchCmd = this.program
28
+ .command('batch')
29
+ .description('batch processing and automation with YAML');
30
+ // Run batch job
31
+ batchCmd
32
+ .command('run <file>')
33
+ .description('run batch job from YAML file')
34
+ .option('--dry-run', 'show what would be executed without running')
35
+ .option('--parallel', 'run commands in parallel where possible')
36
+ .option('--continue-on-error', 'continue execution on command failures')
37
+ .option('--vars <vars>', 'variables to pass to batch job (JSON or key=value)')
38
+ .option('--timeout <ms>', 'global timeout for batch job')
39
+ .action(async (file, options) => {
40
+ await this.runBatchJob(file, options);
41
+ });
42
+ // Create batch job
43
+ batchCmd
44
+ .command('create <name>')
45
+ .description('create a new batch job')
46
+ .option('--template <template>', 'batch job template')
47
+ .option('--commands <commands>', 'commands to include (comma-separated)')
48
+ .option('--interactive', 'create job interactively')
49
+ .action(async (name, options) => {
50
+ await this.createBatchJob(name, options);
51
+ });
52
+ // List batch jobs
53
+ batchCmd
54
+ .command('list')
55
+ .alias('ls')
56
+ .description('list available batch jobs')
57
+ .option('--path <path>', 'custom batch jobs directory')
58
+ .action(async (options) => {
59
+ await this.listBatchJobs(options);
60
+ });
61
+ // Validate batch job
62
+ batchCmd
63
+ .command('validate <file>')
64
+ .description('validate batch job YAML file')
65
+ .action(async (file) => {
66
+ await this.validateBatchJob(file);
67
+ });
68
+ // Stop running job
69
+ batchCmd
70
+ .command('stop <jobId>')
71
+ .description('stop a running batch job')
72
+ .action(async (jobId) => {
73
+ await this.stopBatchJob(jobId);
74
+ });
75
+ // Show job status
76
+ batchCmd
77
+ .command('status [jobId]')
78
+ .description('show batch job status')
79
+ .action(async (jobId) => {
80
+ await this.showJobStatus(jobId);
81
+ });
82
+ // Schedule batch job
83
+ batchCmd
84
+ .command('schedule <file>')
85
+ .description('schedule batch job execution')
86
+ .option('--cron <expression>', 'cron expression for scheduling')
87
+ .option('--interval <ms>', 'interval in milliseconds')
88
+ .option('--once', 'run once after delay')
89
+ .action(async (file, options) => {
90
+ await this.scheduleBatchJob(file, options);
91
+ });
92
+ // Export batch job
93
+ batchCmd
94
+ .command('export <file>')
95
+ .description('export batch job to different formats')
96
+ .option('--format <format>', 'export format (json, shell, dockerfile)', 'json')
97
+ .option('--output <path>', 'output file path')
98
+ .action(async (file, options) => {
99
+ await this.exportBatchJob(file, options);
100
+ });
101
+ // Import batch job
102
+ batchCmd
103
+ .command('import <file>')
104
+ .description('import batch job from different formats')
105
+ .option('--format <format>', 'source format (json, shell, package-scripts)')
106
+ .option('--name <name>', 'batch job name')
107
+ .action(async (file, options) => {
108
+ await this.importBatchJob(file, options);
109
+ });
110
+ // Template management
111
+ batchCmd.command('template').description('manage batch job templates');
112
+ batchCmd
113
+ .command('template list')
114
+ .description('list available templates')
115
+ .action(async () => {
116
+ await this.listTemplates();
117
+ });
118
+ batchCmd
119
+ .command('template create <name>')
120
+ .description('create a new template')
121
+ .option('--from <file>', 'create template from existing batch job')
122
+ .action(async (name, options) => {
123
+ await this.createTemplate(name, options);
124
+ });
125
+ }
126
+ /**
127
+ * Run batch job from YAML file
128
+ */
129
+ async runBatchJob(file, options) {
130
+ try {
131
+ logger_1.logger.info(`Running batch job: ${chalk_1.default.cyan(file)}`);
132
+ // Load and validate batch job
133
+ const job = await this.loadBatchJob(file);
134
+ await this.validateJobStructure(job);
135
+ // Process variables
136
+ const processedJob = await this.processJobVariables(job, options.vars);
137
+ if (options.dryRun) {
138
+ await this.showDryRun(processedJob);
139
+ return;
140
+ }
141
+ // Create job ID and track execution
142
+ const jobId = `job-${Date.now()}`;
143
+ this.runningJobs.set(jobId, {
144
+ file,
145
+ job: processedJob,
146
+ startTime: Date.now(),
147
+ status: 'running',
148
+ });
149
+ try {
150
+ await this.executeBatchJob(processedJob, jobId, options);
151
+ logger_1.logger.success(`Batch job completed: ${file}`);
152
+ }
153
+ finally {
154
+ this.runningJobs.delete(jobId);
155
+ }
156
+ }
157
+ catch (error) {
158
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_RUN_FAILED', 'Failed to run batch job', { file, options }, true);
159
+ }
160
+ }
161
+ /**
162
+ * Create a new batch job
163
+ */
164
+ async createBatchJob(name, options) {
165
+ try {
166
+ logger_1.logger.info(`Creating batch job: ${chalk_1.default.cyan(name)}`);
167
+ let job;
168
+ if (options.interactive) {
169
+ job = await this.createInteractiveBatchJob(name);
170
+ }
171
+ else if (options.template) {
172
+ job = await this.createJobFromTemplate(name, options.template);
173
+ }
174
+ else {
175
+ job = this.createBasicBatchJob(name, options);
176
+ }
177
+ const jobPath = path_1.default.join(process.cwd(), '.wundr', 'batch', `${name}.yaml`);
178
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(jobPath));
179
+ await fs_extra_1.default.writeFile(jobPath, yaml_1.default.stringify(job));
180
+ logger_1.logger.success(`Batch job created: ${jobPath}`);
181
+ }
182
+ catch (error) {
183
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_CREATE_FAILED', 'Failed to create batch job', { name, options }, true);
184
+ }
185
+ }
186
+ /**
187
+ * List available batch jobs
188
+ */
189
+ async listBatchJobs(options) {
190
+ try {
191
+ const batchDir = options.path || path_1.default.join(process.cwd(), '.wundr', 'batch');
192
+ if (!(await fs_extra_1.default.pathExists(batchDir))) {
193
+ logger_1.logger.info('No batch jobs directory found');
194
+ return;
195
+ }
196
+ const files = await fs_extra_1.default.readdir(batchDir);
197
+ const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
198
+ if (yamlFiles.length === 0) {
199
+ logger_1.logger.info('No batch jobs found');
200
+ return;
201
+ }
202
+ logger_1.logger.info(`Batch jobs (${yamlFiles.length}):`);
203
+ const jobs = [];
204
+ for (const file of yamlFiles) {
205
+ const filePath = path_1.default.join(batchDir, file);
206
+ try {
207
+ const job = await this.loadBatchJob(filePath);
208
+ jobs.push({
209
+ Name: path_1.default.basename(file, path_1.default.extname(file)),
210
+ Description: job.description || 'No description',
211
+ Commands: job.commands.length,
212
+ Parallel: job.parallel ? '✓' : '✗',
213
+ });
214
+ }
215
+ catch (error) {
216
+ jobs.push({
217
+ Name: path_1.default.basename(file, path_1.default.extname(file)),
218
+ Description: 'Invalid YAML',
219
+ Commands: 'N/A',
220
+ Parallel: 'N/A',
221
+ });
222
+ }
223
+ }
224
+ console.table(jobs);
225
+ }
226
+ catch (error) {
227
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_LIST_FAILED', 'Failed to list batch jobs', { options }, true);
228
+ }
229
+ }
230
+ /**
231
+ * Validate batch job YAML file
232
+ */
233
+ async validateBatchJob(file) {
234
+ try {
235
+ logger_1.logger.info(`Validating batch job: ${chalk_1.default.cyan(file)}`);
236
+ const job = await this.loadBatchJob(file);
237
+ const validation = await this.validateJobStructure(job);
238
+ if (validation.valid) {
239
+ logger_1.logger.success('Batch job is valid ✓');
240
+ // Show job summary
241
+ console.log(chalk_1.default.blue('\nJob Summary:'));
242
+ console.log(`Name: ${job.name}`);
243
+ console.log(`Description: ${job.description || 'None'}`);
244
+ console.log(`Commands: ${job.commands.length}`);
245
+ console.log(`Parallel execution: ${job.parallel ? 'Yes' : 'No'}`);
246
+ console.log(`Continue on error: ${job.continueOnError ? 'Yes' : 'No'}`);
247
+ }
248
+ else {
249
+ logger_1.logger.error('Batch job validation failed:');
250
+ validation.errors.forEach(error => {
251
+ logger_1.logger.error(` - ${error}`);
252
+ });
253
+ process.exit(1);
254
+ }
255
+ }
256
+ catch (error) {
257
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_VALIDATE_FAILED', 'Failed to validate batch job', { file }, true);
258
+ }
259
+ }
260
+ /**
261
+ * Stop a running batch job
262
+ */
263
+ async stopBatchJob(jobId) {
264
+ try {
265
+ const job = this.runningJobs.get(jobId);
266
+ if (!job) {
267
+ logger_1.logger.warn(`Job not found: ${jobId}`);
268
+ return;
269
+ }
270
+ // Stop the job (implementation would depend on how jobs are executed)
271
+ job.status = 'stopped';
272
+ this.runningJobs.delete(jobId);
273
+ logger_1.logger.success(`Batch job stopped: ${jobId}`);
274
+ }
275
+ catch (error) {
276
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_STOP_FAILED', 'Failed to stop batch job', { jobId }, true);
277
+ }
278
+ }
279
+ /**
280
+ * Show job status
281
+ */
282
+ async showJobStatus(jobId) {
283
+ try {
284
+ if (jobId) {
285
+ const job = this.runningJobs.get(jobId);
286
+ if (!job) {
287
+ logger_1.logger.warn(`Job not found: ${jobId}`);
288
+ return;
289
+ }
290
+ console.log(chalk_1.default.blue(`\nJob Status: ${jobId}`));
291
+ console.log(`File: ${job.file}`);
292
+ console.log(`Status: ${job.status}`);
293
+ console.log(`Started: ${new Date(job.startTime).toLocaleString()}`);
294
+ console.log(`Duration: ${Date.now() - job.startTime}ms`);
295
+ }
296
+ else {
297
+ if (this.runningJobs.size === 0) {
298
+ logger_1.logger.info('No running batch jobs');
299
+ return;
300
+ }
301
+ console.log(chalk_1.default.blue(`\nRunning Jobs (${this.runningJobs.size}):`));
302
+ const jobData = Array.from(this.runningJobs.entries()).map(([id, job]) => ({
303
+ ID: id,
304
+ File: path_1.default.basename(job.file),
305
+ Status: job.status,
306
+ Duration: `${Date.now() - job.startTime}ms`,
307
+ }));
308
+ console.table(jobData);
309
+ }
310
+ }
311
+ catch (error) {
312
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_STATUS_FAILED', 'Failed to show job status', { jobId }, true);
313
+ }
314
+ }
315
+ /**
316
+ * Schedule batch job execution
317
+ */
318
+ async scheduleBatchJob(file, options) {
319
+ try {
320
+ logger_1.logger.info(`Scheduling batch job: ${chalk_1.default.cyan(file)}`);
321
+ // Validate the job first
322
+ await this.loadBatchJob(file);
323
+ if (options.cron) {
324
+ logger_1.logger.info(`Scheduled with cron: ${options.cron}`);
325
+ // Implementation would use a cron library
326
+ }
327
+ else if (options.interval) {
328
+ logger_1.logger.info(`Scheduled with interval: ${options.interval}ms`);
329
+ // Implementation would use setInterval
330
+ }
331
+ else if (options.once) {
332
+ logger_1.logger.info('Scheduled to run once');
333
+ // Implementation would use setTimeout
334
+ }
335
+ else {
336
+ throw new Error('No scheduling option provided');
337
+ }
338
+ logger_1.logger.success('Batch job scheduled successfully');
339
+ }
340
+ catch (error) {
341
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_SCHEDULE_FAILED', 'Failed to schedule batch job', { file, options }, true);
342
+ }
343
+ }
344
+ /**
345
+ * Export batch job to different formats
346
+ */
347
+ async exportBatchJob(file, options) {
348
+ try {
349
+ logger_1.logger.info(`Exporting batch job: ${chalk_1.default.cyan(file)}`);
350
+ const job = await this.loadBatchJob(file);
351
+ let exportedContent;
352
+ switch (options.format) {
353
+ case 'json':
354
+ exportedContent = JSON.stringify(job, null, 2);
355
+ break;
356
+ case 'shell':
357
+ exportedContent = this.convertToShellScript(job);
358
+ break;
359
+ case 'dockerfile':
360
+ exportedContent = this.convertToDockerfile(job);
361
+ break;
362
+ default:
363
+ throw new Error(`Unsupported export format: ${options.format}`);
364
+ }
365
+ const outputPath = options.output ||
366
+ `${path_1.default.basename(file, path_1.default.extname(file))}.${options.format}`;
367
+ await fs_extra_1.default.writeFile(outputPath, exportedContent);
368
+ logger_1.logger.success(`Batch job exported: ${outputPath}`);
369
+ }
370
+ catch (error) {
371
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_EXPORT_FAILED', 'Failed to export batch job', { file, options }, true);
372
+ }
373
+ }
374
+ /**
375
+ * Import batch job from different formats
376
+ */
377
+ async importBatchJob(file, options) {
378
+ try {
379
+ logger_1.logger.info(`Importing batch job: ${chalk_1.default.cyan(file)}`);
380
+ let job;
381
+ switch (options.format) {
382
+ case 'json':
383
+ job = await this.importFromJSON(file, options.name);
384
+ break;
385
+ case 'shell':
386
+ job = await this.importFromShell(file, options.name);
387
+ break;
388
+ case 'package-scripts':
389
+ job = await this.importFromPackageScripts(file, options.name);
390
+ break;
391
+ default:
392
+ throw new Error(`Unsupported import format: ${options.format}`);
393
+ }
394
+ const jobName = options.name || path_1.default.basename(file, path_1.default.extname(file));
395
+ const jobPath = path_1.default.join(process.cwd(), '.wundr', 'batch', `${jobName}.yaml`);
396
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(jobPath));
397
+ await fs_extra_1.default.writeFile(jobPath, yaml_1.default.stringify(job));
398
+ logger_1.logger.success(`Batch job imported: ${jobPath}`);
399
+ }
400
+ catch (error) {
401
+ throw error_handler_1.errorHandler.createError('WUNDR_BATCH_IMPORT_FAILED', 'Failed to import batch job', { file, options }, true);
402
+ }
403
+ }
404
+ /**
405
+ * Helper methods for batch job operations
406
+ */
407
+ async loadBatchJob(file) {
408
+ if (!(await fs_extra_1.default.pathExists(file))) {
409
+ throw new Error(`Batch job file not found: ${file}`);
410
+ }
411
+ const content = await fs_extra_1.default.readFile(file, 'utf8');
412
+ const ext = path_1.default.extname(file).toLowerCase();
413
+ if (ext === '.yaml' || ext === '.yml') {
414
+ return yaml_1.default.parse(content);
415
+ }
416
+ else if (ext === '.json') {
417
+ return JSON.parse(content);
418
+ }
419
+ else {
420
+ throw new Error(`Unsupported file format: ${ext}`);
421
+ }
422
+ }
423
+ async validateJobStructure(job) {
424
+ const errors = [];
425
+ if (!job.name) {
426
+ errors.push('Job name is required');
427
+ }
428
+ if (!job.commands || job.commands.length === 0) {
429
+ errors.push('At least one command is required');
430
+ }
431
+ else {
432
+ job.commands.forEach((cmd, index) => {
433
+ if (!cmd.command) {
434
+ errors.push(`Command ${index + 1} is missing command property`);
435
+ }
436
+ });
437
+ }
438
+ return { valid: errors.length === 0, errors };
439
+ }
440
+ /**
441
+ * Regex for valid variable names in batch templates.
442
+ */
443
+ static VALID_VARIABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
444
+ async processJobVariables(job, vars) {
445
+ let variables = {};
446
+ if (vars) {
447
+ try {
448
+ // Try parsing as JSON first
449
+ variables = JSON.parse(vars);
450
+ }
451
+ catch {
452
+ // Parse as key=value pairs
453
+ vars.split(',').forEach(pair => {
454
+ const eqIndex = pair.indexOf('=');
455
+ if (eqIndex > 0) {
456
+ const key = pair.slice(0, eqIndex).trim();
457
+ const value = pair.slice(eqIndex + 1).trim();
458
+ if (key) {
459
+ variables[key] = value;
460
+ }
461
+ }
462
+ });
463
+ }
464
+ }
465
+ // Validate variable names to prevent regex injection
466
+ for (const key of Object.keys(variables)) {
467
+ if (!BatchCommands.VALID_VARIABLE_NAME.test(key)) {
468
+ throw new Error(`Invalid variable name "${key}": must match [a-zA-Z_][a-zA-Z0-9_]*`);
469
+ }
470
+ }
471
+ // SECURITY: Instead of replacing {{var}} in a JSON-stringified string
472
+ // (which allows JSON structure breakout via quotes in values), we walk
473
+ // the job structure and only replace in string leaf values. Values are
474
+ // JSON-escaped to prevent structure injection.
475
+ const processedJob = JSON.parse(JSON.stringify(job));
476
+ const replaceVarsInString = (str) => {
477
+ let result = str;
478
+ for (const [key, value] of Object.entries(variables)) {
479
+ const placeholder = `{{${key}}}`;
480
+ // Only replace exact placeholder matches, not regex patterns
481
+ while (result.includes(placeholder)) {
482
+ result = result.replace(placeholder, String(value));
483
+ }
484
+ }
485
+ return result;
486
+ };
487
+ // Walk commands and replace variables in string fields only
488
+ processedJob.commands = processedJob.commands.map(cmd => ({
489
+ ...cmd,
490
+ command: replaceVarsInString(cmd.command),
491
+ args: cmd.args?.map(arg => replaceVarsInString(arg)),
492
+ condition: cmd.condition ? replaceVarsInString(cmd.condition) : undefined,
493
+ }));
494
+ if (processedJob.description) {
495
+ processedJob.description = replaceVarsInString(processedJob.description);
496
+ }
497
+ return processedJob;
498
+ }
499
+ async showDryRun(job) {
500
+ console.log(chalk_1.default.blue('\nDry Run - Commands to be executed:'));
501
+ console.log(chalk_1.default.gray('='.repeat(50)));
502
+ job.commands.forEach((cmd, index) => {
503
+ console.log(`${index + 1}. ${chalk_1.default.cyan(cmd.command)}`);
504
+ if (cmd.args) {
505
+ console.log(` Args: ${cmd.args.join(' ')}`);
506
+ }
507
+ if (cmd.condition) {
508
+ console.log(` Condition: ${cmd.condition}`);
509
+ }
510
+ if (cmd.retry) {
511
+ console.log(` Retry: ${cmd.retry} times`);
512
+ }
513
+ if (cmd.timeout) {
514
+ console.log(` Timeout: ${cmd.timeout}ms`);
515
+ }
516
+ console.log();
517
+ });
518
+ console.log(chalk_1.default.gray('='.repeat(50)));
519
+ console.log(`Total commands: ${job.commands.length}`);
520
+ console.log(`Parallel execution: ${job.parallel ? 'Yes' : 'No'}`);
521
+ console.log(`Continue on error: ${job.continueOnError ? 'Yes' : 'No'}`);
522
+ }
523
+ async executeBatchJob(job, _jobId, _options) {
524
+ const tasks = job.commands.map((cmd, _index) => ({
525
+ title: cmd.command,
526
+ task: async () => {
527
+ await this.executeCommand(cmd, _options);
528
+ },
529
+ retry: cmd.retry || 0,
530
+ timeout: cmd.timeout,
531
+ }));
532
+ const listr = new listr2_1.Listr(tasks, {
533
+ concurrent: job.parallel || _options.parallel,
534
+ exitOnError: !(job.continueOnError || _options.continueOnError),
535
+ rendererOptions: {
536
+ showSubtasks: true,
537
+ },
538
+ });
539
+ await listr.run();
540
+ }
541
+ /**
542
+ * Shell metacharacters that indicate injection attempts.
543
+ * These characters have special meaning in sh/bash and must not
544
+ * appear in arguments passed to spawn().
545
+ */
546
+ static SHELL_METACHARACTERS = /[;&|`$(){}[\]<>!#~*?\n\r\\]/;
547
+ /**
548
+ * Tokenize a command string into [binary, ...args] without shell
549
+ * interpretation. Supports simple single/double quoting.
550
+ */
551
+ tokenizeCommand(command) {
552
+ const tokens = [];
553
+ let current = '';
554
+ let inSingle = false;
555
+ let inDouble = false;
556
+ for (let i = 0; i < command.length; i++) {
557
+ const ch = command[i];
558
+ if (inSingle) {
559
+ if (ch === "'") {
560
+ inSingle = false;
561
+ }
562
+ else {
563
+ current += ch;
564
+ }
565
+ continue;
566
+ }
567
+ if (inDouble) {
568
+ if (ch === '"') {
569
+ inDouble = false;
570
+ }
571
+ else {
572
+ current += ch;
573
+ }
574
+ continue;
575
+ }
576
+ if (ch === "'") {
577
+ inSingle = true;
578
+ continue;
579
+ }
580
+ if (ch === '"') {
581
+ inDouble = true;
582
+ continue;
583
+ }
584
+ if (ch === ' ' || ch === '\t') {
585
+ if (current.length > 0) {
586
+ tokens.push(current);
587
+ current = '';
588
+ }
589
+ continue;
590
+ }
591
+ current += ch;
592
+ }
593
+ if (current.length > 0) {
594
+ tokens.push(current);
595
+ }
596
+ if (inSingle || inDouble) {
597
+ throw new Error(`Unterminated quote in command: ${command}`);
598
+ }
599
+ return tokens;
600
+ }
601
+ /**
602
+ * Validate that no argument contains shell metacharacters.
603
+ * This is a defense-in-depth measure: since we never use shell: true,
604
+ * metacharacters would be treated literally, but their presence strongly
605
+ * suggests a command injection attempt.
606
+ */
607
+ validateArgs(args) {
608
+ for (const arg of args) {
609
+ if (BatchCommands.SHELL_METACHARACTERS.test(arg)) {
610
+ throw new Error(`Argument contains shell metacharacters (possible injection): "${arg}"`);
611
+ }
612
+ }
613
+ }
614
+ async executeCommand(cmd, _options) {
615
+ // Check condition if specified
616
+ if (cmd.condition && !(await this.evaluateCondition(cmd.condition))) {
617
+ logger_1.logger.debug(`Skipping command due to condition: ${cmd.condition}`);
618
+ return;
619
+ }
620
+ const { spawn } = await Promise.resolve().then(() => tslib_1.__importStar(require('child_process')));
621
+ // Tokenize the command string into binary + args without shell
622
+ // interpretation. This prevents command injection via shell metacharacters
623
+ // like ; | & ` $() etc.
624
+ const tokens = this.tokenizeCommand(cmd.command);
625
+ if (tokens.length === 0) {
626
+ throw new Error('Empty command');
627
+ }
628
+ const [command, ...parsedArgs] = tokens;
629
+ const finalArgs = cmd.args ? [...parsedArgs, ...cmd.args] : parsedArgs;
630
+ // Validate arguments for shell metacharacters as defense-in-depth
631
+ this.validateArgs(finalArgs);
632
+ return new Promise((resolve, reject) => {
633
+ // SECURITY: No shell: true. The command is executed directly via
634
+ // execvp(), so shell metacharacters in arguments are treated as
635
+ // literal characters rather than being interpreted by a shell.
636
+ const child = spawn(command ?? 'echo', finalArgs, {
637
+ stdio: ['ignore', 'pipe', 'pipe'],
638
+ });
639
+ let output = '';
640
+ let error = '';
641
+ child.stdout?.on('data', (data) => {
642
+ output += data.toString();
643
+ });
644
+ child.stderr?.on('data', (data) => {
645
+ error += data.toString();
646
+ });
647
+ child.on('exit', (code) => {
648
+ if (code === 0) {
649
+ resolve();
650
+ }
651
+ else {
652
+ reject(new Error(`Command failed with exit code ${code}: ${error}`));
653
+ }
654
+ });
655
+ // Handle timeout
656
+ if (cmd.timeout) {
657
+ setTimeout(() => {
658
+ child.kill();
659
+ reject(new Error(`Command timed out after ${cmd.timeout}ms`));
660
+ }, cmd.timeout);
661
+ }
662
+ });
663
+ }
664
+ async evaluateCondition(condition) {
665
+ // Simple condition evaluation
666
+ // In a real implementation, this would be more sophisticated
667
+ switch (condition) {
668
+ case 'always':
669
+ return true;
670
+ case 'never':
671
+ return false;
672
+ default:
673
+ // Could evaluate file existence, environment variables, etc.
674
+ return true;
675
+ }
676
+ }
677
+ async createInteractiveBatchJob(name) {
678
+ const inquirer = await Promise.resolve().then(() => tslib_1.__importStar(require('inquirer')));
679
+ const answers = await inquirer.default.prompt([
680
+ {
681
+ type: 'input',
682
+ name: 'description',
683
+ message: 'Job description:',
684
+ },
685
+ {
686
+ type: 'confirm',
687
+ name: 'parallel',
688
+ message: 'Run commands in parallel?',
689
+ default: false,
690
+ },
691
+ {
692
+ type: 'confirm',
693
+ name: 'continueOnError',
694
+ message: 'Continue on command failure?',
695
+ default: false,
696
+ },
697
+ ]);
698
+ const commands = [];
699
+ let addMore = true;
700
+ while (addMore) {
701
+ const cmdAnswers = await inquirer.default.prompt([
702
+ {
703
+ type: 'input',
704
+ name: 'command',
705
+ message: 'Command:',
706
+ validate: input => input.length > 0 || 'Command is required',
707
+ },
708
+ {
709
+ type: 'input',
710
+ name: 'condition',
711
+ message: 'Condition (optional):',
712
+ },
713
+ {
714
+ type: 'number',
715
+ name: 'retry',
716
+ message: 'Retry count:',
717
+ default: 0,
718
+ },
719
+ {
720
+ type: 'confirm',
721
+ name: 'addMore',
722
+ message: 'Add another command?',
723
+ default: true,
724
+ },
725
+ ]);
726
+ commands.push({
727
+ command: cmdAnswers.command,
728
+ condition: cmdAnswers.condition || undefined,
729
+ retry: cmdAnswers.retry || undefined,
730
+ });
731
+ addMore = cmdAnswers.addMore;
732
+ }
733
+ return {
734
+ name,
735
+ description: answers.description,
736
+ commands,
737
+ parallel: answers.parallel,
738
+ continueOnError: answers.continueOnError,
739
+ };
740
+ }
741
+ createBasicBatchJob(name, options) {
742
+ const commands = options.commands
743
+ ? options.commands
744
+ .split(',')
745
+ .map((cmd) => ({ command: cmd.trim() }))
746
+ : [];
747
+ return {
748
+ name,
749
+ description: `Batch job: ${name}`,
750
+ commands,
751
+ parallel: false,
752
+ continueOnError: false,
753
+ };
754
+ }
755
+ async createJobFromTemplate(name, template) {
756
+ // Load template and create job
757
+ const templatePath = path_1.default.join(__dirname, '../../templates/batch', `${template}.yaml`);
758
+ if (await fs_extra_1.default.pathExists(templatePath)) {
759
+ const templateJob = await this.loadBatchJob(templatePath);
760
+ return { ...templateJob, name };
761
+ }
762
+ throw new Error(`Template not found: ${template}`);
763
+ }
764
+ // Format conversion methods
765
+ convertToShellScript(job) {
766
+ let script = `#!/bin/bash\n# Generated from batch job: ${job.name}\n\n`;
767
+ if (job.description) {
768
+ script += `# ${job.description}\n\n`;
769
+ }
770
+ script += 'set -e\n\n'; // Exit on error unless continueOnError is true
771
+ job.commands.forEach(cmd => {
772
+ script += `echo "Executing: ${cmd.command}"\n`;
773
+ script += `${cmd.command}\n\n`;
774
+ });
775
+ return script;
776
+ }
777
+ convertToDockerfile(job) {
778
+ let dockerfile = `# Generated from batch job: ${job.name}\n`;
779
+ dockerfile += 'FROM node:18-alpine\n\n';
780
+ if (job.description) {
781
+ dockerfile += `# ${job.description}\n`;
782
+ }
783
+ dockerfile += 'WORKDIR /app\n';
784
+ dockerfile += 'COPY . .\n\n';
785
+ job.commands.forEach(cmd => {
786
+ dockerfile += `RUN ${cmd.command}\n`;
787
+ });
788
+ return dockerfile;
789
+ }
790
+ async importFromJSON(file, name) {
791
+ const content = await fs_extra_1.default.readJson(file);
792
+ return { ...content, name: name || content.name };
793
+ }
794
+ async importFromShell(file, name) {
795
+ const content = await fs_extra_1.default.readFile(file, 'utf8');
796
+ const commands = content
797
+ .split('\n')
798
+ .filter(line => line.trim() && !line.startsWith('#'))
799
+ .map(line => ({ command: line.trim() }));
800
+ return {
801
+ name: name || path_1.default.basename(file, path_1.default.extname(file)),
802
+ description: `Imported from shell script: ${file}`,
803
+ commands,
804
+ };
805
+ }
806
+ async importFromPackageScripts(file, name) {
807
+ const packageJson = await fs_extra_1.default.readJson(file);
808
+ const scripts = packageJson.scripts || {};
809
+ const commands = Object.entries(scripts).map(([script, command]) => ({
810
+ command: `npm run ${script}`,
811
+ args: [],
812
+ condition: undefined,
813
+ }));
814
+ return {
815
+ name: name || 'package-scripts',
816
+ description: 'Imported from package.json scripts',
817
+ commands,
818
+ };
819
+ }
820
+ async listTemplates() {
821
+ const templatesDir = path_1.default.join(__dirname, '../../templates/batch');
822
+ if (await fs_extra_1.default.pathExists(templatesDir)) {
823
+ const templates = await fs_extra_1.default.readdir(templatesDir);
824
+ const yamlTemplates = templates.filter(t => t.endsWith('.yaml') || t.endsWith('.yml'));
825
+ if (yamlTemplates.length > 0) {
826
+ logger_1.logger.info('Available templates:');
827
+ yamlTemplates.forEach(template => {
828
+ console.log(` - ${path_1.default.basename(template, path_1.default.extname(template))}`);
829
+ });
830
+ }
831
+ else {
832
+ logger_1.logger.info('No templates available');
833
+ }
834
+ }
835
+ else {
836
+ logger_1.logger.info('No templates directory found');
837
+ }
838
+ }
839
+ async createTemplate(name, options) {
840
+ logger_1.logger.info(`Creating template: ${name}`);
841
+ if (options.from) {
842
+ const job = await this.loadBatchJob(options.from);
843
+ const templatePath = path_1.default.join(__dirname, '../../templates/batch', `${name}.yaml`);
844
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(templatePath));
845
+ await fs_extra_1.default.writeFile(templatePath, yaml_1.default.stringify(job));
846
+ logger_1.logger.success(`Template created: ${templatePath}`);
847
+ }
848
+ else {
849
+ logger_1.logger.info('Template creation from scratch not yet implemented');
850
+ }
851
+ }
852
+ }
853
+ exports.BatchCommands = BatchCommands;
854
+ //# sourceMappingURL=batch.js.map