@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,848 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+ import { ConfigManager } from '../utils/config-manager';
6
+ import { PluginManager } from '../plugins/plugin-manager';
7
+ import { logger } from '../utils/logger';
8
+ import { errorHandler } from '../utils/error-handler';
9
+
10
+ /**
11
+ * Plugin commands for managing CLI extensions
12
+ */
13
+ export class PluginCommands {
14
+ constructor(
15
+ private program: Command,
16
+ private configManager: ConfigManager,
17
+ private pluginManager: PluginManager
18
+ ) {
19
+ this.registerCommands();
20
+ }
21
+
22
+ private registerCommands(): void {
23
+ const pluginCmd = this.program
24
+ .command('plugin')
25
+ .alias('plugins')
26
+ .description('manage CLI plugins and extensions');
27
+
28
+ // List plugins
29
+ pluginCmd
30
+ .command('list')
31
+ .alias('ls')
32
+ .description('list installed plugins')
33
+ .option('--available', 'show available plugins from registry')
34
+ .option('--enabled-only', 'show only enabled plugins')
35
+ .action(async options => {
36
+ await this.listPlugins(options);
37
+ });
38
+
39
+ // Install plugin
40
+ pluginCmd
41
+ .command('install <plugin>')
42
+ .description('install a plugin')
43
+ .option('--version <version>', 'specific version to install')
44
+ .option('--registry <url>', 'custom registry URL')
45
+ .option('--force', 'force reinstall if already exists')
46
+ .action(async (plugin, options) => {
47
+ await this.installPlugin(plugin, options);
48
+ });
49
+
50
+ // Uninstall plugin
51
+ pluginCmd
52
+ .command('uninstall <plugin>')
53
+ .alias('remove')
54
+ .description('uninstall a plugin')
55
+ .option('--force', 'skip confirmation')
56
+ .action(async (plugin, options) => {
57
+ await this.uninstallPlugin(plugin, options);
58
+ });
59
+
60
+ // Enable plugin
61
+ pluginCmd
62
+ .command('enable <plugin>')
63
+ .description('enable a plugin')
64
+ .action(async plugin => {
65
+ await this.enablePlugin(plugin);
66
+ });
67
+
68
+ // Disable plugin
69
+ pluginCmd
70
+ .command('disable <plugin>')
71
+ .description('disable a plugin')
72
+ .action(async plugin => {
73
+ await this.disablePlugin(plugin);
74
+ });
75
+
76
+ // Plugin info
77
+ pluginCmd
78
+ .command('info <plugin>')
79
+ .description('show plugin information')
80
+ .action(async plugin => {
81
+ await this.showPluginInfo(plugin);
82
+ });
83
+
84
+ // Update plugins
85
+ pluginCmd
86
+ .command('update [plugin]')
87
+ .description('update plugin(s)')
88
+ .option('--all', 'update all plugins')
89
+ .action(async (plugin, options) => {
90
+ await this.updatePlugin(plugin, options);
91
+ });
92
+
93
+ // Create plugin
94
+ pluginCmd
95
+ .command('create <name>')
96
+ .description('create a new plugin')
97
+ .option('--template <template>', 'plugin template', 'basic')
98
+ .option('--interactive', 'create plugin interactively')
99
+ .action(async (name, options) => {
100
+ await this.createPlugin(name, options);
101
+ });
102
+
103
+ // Publish plugin
104
+ pluginCmd
105
+ .command('publish')
106
+ .description('publish plugin to registry')
107
+ .option('--registry <url>', 'registry URL')
108
+ .option('--dry-run', 'show what would be published')
109
+ .action(async options => {
110
+ await this.publishPlugin(options);
111
+ });
112
+
113
+ // Search plugins
114
+ pluginCmd
115
+ .command('search <query>')
116
+ .description('search for plugins')
117
+ .option('--registry <url>', 'registry URL')
118
+ .option('--limit <count>', 'limit results', '20')
119
+ .action(async (query, options) => {
120
+ await this.searchPlugins(query, options);
121
+ });
122
+
123
+ // Plugin development
124
+ pluginCmd.command('dev').description('plugin development tools');
125
+
126
+ pluginCmd
127
+ .command('dev link <path>')
128
+ .description('link local plugin for development')
129
+ .action(async pluginPath => {
130
+ await this.linkPlugin(pluginPath);
131
+ });
132
+
133
+ pluginCmd
134
+ .command('dev unlink <plugin>')
135
+ .description('unlink development plugin')
136
+ .action(async plugin => {
137
+ await this.unlinkPlugin(plugin);
138
+ });
139
+
140
+ pluginCmd
141
+ .command('dev test <plugin>')
142
+ .description('test a plugin')
143
+ .option('--coverage', 'run with coverage')
144
+ .action(async (plugin, options) => {
145
+ await this.testPlugin(plugin, options);
146
+ });
147
+
148
+ // Plugin configuration
149
+ pluginCmd
150
+ .command('config <plugin>')
151
+ .description('configure plugin settings');
152
+
153
+ pluginCmd
154
+ .command('config <plugin> set <key> <value>')
155
+ .description('set plugin configuration')
156
+ .action(async (plugin, key, value) => {
157
+ await this.setPluginConfig(plugin, key, value);
158
+ });
159
+
160
+ pluginCmd
161
+ .command('config <plugin> get [key]')
162
+ .description('get plugin configuration')
163
+ .action(async (plugin, key) => {
164
+ await this.getPluginConfig(plugin, key);
165
+ });
166
+ }
167
+
168
+ /**
169
+ * List installed plugins
170
+ */
171
+ private async listPlugins(options: any): Promise<void> {
172
+ try {
173
+ if (options.available) {
174
+ await this.listAvailablePlugins();
175
+ } else {
176
+ await this.listInstalledPlugins(options.enabledOnly);
177
+ }
178
+ } catch (error) {
179
+ throw errorHandler.createError(
180
+ 'WUNDR_PLUGIN_LIST_FAILED',
181
+ 'Failed to list plugins',
182
+ { options },
183
+ true
184
+ );
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Install a plugin
190
+ */
191
+ private async installPlugin(plugin: string, options: any): Promise<void> {
192
+ try {
193
+ logger.info(`Installing plugin: ${chalk.cyan(plugin)}`);
194
+
195
+ // Check if plugin already exists
196
+ if (
197
+ !options.force &&
198
+ (await this.pluginManager.isPluginInstalled(plugin))
199
+ ) {
200
+ logger.warn(
201
+ `Plugin ${plugin} is already installed. Use --force to reinstall.`
202
+ );
203
+ return;
204
+ }
205
+
206
+ await this.pluginManager.installPlugin(plugin, {
207
+ version: options.version,
208
+ registry: options.registry,
209
+ force: options.force,
210
+ });
211
+
212
+ logger.success(`Plugin ${plugin} installed successfully`);
213
+ } catch (error) {
214
+ throw errorHandler.createError(
215
+ 'WUNDR_PLUGIN_INSTALL_FAILED',
216
+ 'Failed to install plugin',
217
+ { plugin, options },
218
+ true
219
+ );
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Uninstall a plugin
225
+ */
226
+ private async uninstallPlugin(plugin: string, options: any): Promise<void> {
227
+ try {
228
+ if (!(await this.pluginManager.isPluginInstalled(plugin))) {
229
+ logger.warn(`Plugin ${plugin} is not installed`);
230
+ return;
231
+ }
232
+
233
+ if (!options.force) {
234
+ const inquirer = await import('inquirer');
235
+ const { confirm } = await inquirer.default.prompt([
236
+ {
237
+ type: 'confirm',
238
+ name: 'confirm',
239
+ message: `Uninstall plugin ${plugin}?`,
240
+ default: false,
241
+ },
242
+ ]);
243
+
244
+ if (!confirm) {
245
+ logger.info('Uninstall cancelled');
246
+ return;
247
+ }
248
+ }
249
+
250
+ await this.pluginManager.uninstallPlugin(plugin);
251
+ logger.success(`Plugin ${plugin} uninstalled successfully`);
252
+ } catch (error) {
253
+ throw errorHandler.createError(
254
+ 'WUNDR_PLUGIN_UNINSTALL_FAILED',
255
+ 'Failed to uninstall plugin',
256
+ { plugin, options },
257
+ true
258
+ );
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Enable a plugin
264
+ */
265
+ private async enablePlugin(plugin: string): Promise<void> {
266
+ try {
267
+ if (!(await this.pluginManager.isPluginInstalled(plugin))) {
268
+ throw new Error(`Plugin ${plugin} is not installed`);
269
+ }
270
+
271
+ await this.pluginManager.enablePlugin(plugin);
272
+ logger.success(`Plugin ${plugin} enabled`);
273
+ } catch (error) {
274
+ throw errorHandler.createError(
275
+ 'WUNDR_PLUGIN_ENABLE_FAILED',
276
+ 'Failed to enable plugin',
277
+ { plugin },
278
+ true
279
+ );
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Disable a plugin
285
+ */
286
+ private async disablePlugin(plugin: string): Promise<void> {
287
+ try {
288
+ if (!(await this.pluginManager.isPluginInstalled(plugin))) {
289
+ throw new Error(`Plugin ${plugin} is not installed`);
290
+ }
291
+
292
+ await this.pluginManager.disablePlugin(plugin);
293
+ logger.success(`Plugin ${plugin} disabled`);
294
+ } catch (error) {
295
+ throw errorHandler.createError(
296
+ 'WUNDR_PLUGIN_DISABLE_FAILED',
297
+ 'Failed to disable plugin',
298
+ { plugin },
299
+ true
300
+ );
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Show plugin information
306
+ */
307
+ private async showPluginInfo(plugin: string): Promise<void> {
308
+ try {
309
+ const pluginInfo = await this.pluginManager.getPluginInfo(plugin);
310
+
311
+ if (!pluginInfo) {
312
+ logger.warn(`Plugin ${plugin} not found`);
313
+ return;
314
+ }
315
+
316
+ console.log(chalk.blue(`\nPlugin Information: ${plugin}`));
317
+ console.log(`Name: ${pluginInfo.name}`);
318
+ console.log(`Version: ${pluginInfo.version}`);
319
+ console.log(`Description: ${pluginInfo.description || 'No description'}`);
320
+ console.log(`Author: ${pluginInfo.author || 'Unknown'}`);
321
+ console.log(`Status: ${pluginInfo.enabled ? 'Enabled' : 'Disabled'}`);
322
+ console.log(`Commands: ${pluginInfo.commands?.length || 0}`);
323
+ console.log(`Hooks: ${pluginInfo.hooks?.length || 0}`);
324
+
325
+ if (pluginInfo.dependencies && pluginInfo.dependencies.length > 0) {
326
+ console.log(`Dependencies: ${pluginInfo.dependencies.join(', ')}`);
327
+ }
328
+ } catch (error) {
329
+ throw errorHandler.createError(
330
+ 'WUNDR_PLUGIN_INFO_FAILED',
331
+ 'Failed to show plugin info',
332
+ { plugin },
333
+ true
334
+ );
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Update plugin(s)
340
+ */
341
+ private async updatePlugin(plugin: string, options: any): Promise<void> {
342
+ try {
343
+ if (options.all) {
344
+ logger.info('Updating all plugins...');
345
+ await this.pluginManager.updateAllPlugins();
346
+ logger.success('All plugins updated');
347
+ } else if (plugin) {
348
+ logger.info(`Updating plugin: ${chalk.cyan(plugin)}`);
349
+ await this.pluginManager.updatePlugin(plugin);
350
+ logger.success(`Plugin ${plugin} updated`);
351
+ } else {
352
+ throw new Error('Specify a plugin name or use --all');
353
+ }
354
+ } catch (error) {
355
+ throw errorHandler.createError(
356
+ 'WUNDR_PLUGIN_UPDATE_FAILED',
357
+ 'Failed to update plugin(s)',
358
+ { plugin, options },
359
+ true
360
+ );
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Create a new plugin
366
+ */
367
+ private async createPlugin(name: string, options: any): Promise<void> {
368
+ try {
369
+ logger.info(`Creating plugin: ${chalk.cyan(name)}`);
370
+
371
+ let pluginConfig: any;
372
+
373
+ if (options.interactive) {
374
+ pluginConfig = await this.createInteractivePlugin(name);
375
+ } else {
376
+ pluginConfig = await this.createPluginFromTemplate(
377
+ name,
378
+ options.template
379
+ );
380
+ }
381
+
382
+ const pluginPath = path.join(process.cwd(), name);
383
+ await this.generatePluginStructure(pluginPath, pluginConfig);
384
+
385
+ logger.success(`Plugin ${name} created at ${pluginPath}`);
386
+ logger.info('Next steps:');
387
+ logger.info(` cd ${name}`);
388
+ logger.info(' npm install');
389
+ logger.info(' wundr plugin dev link .');
390
+ } catch (error) {
391
+ throw errorHandler.createError(
392
+ 'WUNDR_PLUGIN_CREATE_FAILED',
393
+ 'Failed to create plugin',
394
+ { name, options },
395
+ true
396
+ );
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Publish plugin to registry
402
+ */
403
+ private async publishPlugin(options: any): Promise<void> {
404
+ try {
405
+ logger.info('Publishing plugin...');
406
+
407
+ if (options.dryRun) {
408
+ logger.info('Dry run - would publish:');
409
+ // Show what would be published
410
+ } else {
411
+ await this.pluginManager.publishPlugin({
412
+ registry: options.registry,
413
+ });
414
+ logger.success('Plugin published successfully');
415
+ }
416
+ } catch (error) {
417
+ throw errorHandler.createError(
418
+ 'WUNDR_PLUGIN_PUBLISH_FAILED',
419
+ 'Failed to publish plugin',
420
+ { options },
421
+ true
422
+ );
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Search for plugins
428
+ */
429
+ private async searchPlugins(query: string, options: any): Promise<void> {
430
+ try {
431
+ logger.info(`Searching for plugins: ${chalk.cyan(query)}`);
432
+
433
+ const results = await this.pluginManager.searchPlugins(query, {
434
+ registry: options.registry,
435
+ limit: parseInt(options.limit),
436
+ });
437
+
438
+ if (results.length === 0) {
439
+ logger.info('No plugins found');
440
+ return;
441
+ }
442
+
443
+ console.log(`\nFound ${results.length} plugin(s):`);
444
+ console.table(
445
+ results.map(plugin => ({
446
+ Name: plugin.name,
447
+ Version: plugin.version,
448
+ Description: plugin.description || 'No description',
449
+ Downloads: plugin.downloads || 0,
450
+ Updated: plugin.updated
451
+ ? new Date(plugin.updated).toLocaleDateString()
452
+ : 'Unknown',
453
+ }))
454
+ );
455
+ } catch (error) {
456
+ throw errorHandler.createError(
457
+ 'WUNDR_PLUGIN_SEARCH_FAILED',
458
+ 'Failed to search plugins',
459
+ { query, options },
460
+ true
461
+ );
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Link local plugin for development
467
+ */
468
+ private async linkPlugin(pluginPath: string): Promise<void> {
469
+ try {
470
+ const absolutePath = path.resolve(pluginPath);
471
+
472
+ if (!(await fs.pathExists(absolutePath))) {
473
+ throw new Error(`Plugin path does not exist: ${absolutePath}`);
474
+ }
475
+
476
+ await this.pluginManager.linkPlugin(absolutePath);
477
+ logger.success(`Plugin linked: ${absolutePath}`);
478
+ } catch (error) {
479
+ throw errorHandler.createError(
480
+ 'WUNDR_PLUGIN_LINK_FAILED',
481
+ 'Failed to link plugin',
482
+ { pluginPath },
483
+ true
484
+ );
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Unlink development plugin
490
+ */
491
+ private async unlinkPlugin(plugin: string): Promise<void> {
492
+ try {
493
+ await this.pluginManager.unlinkPlugin(plugin);
494
+ logger.success(`Plugin unlinked: ${plugin}`);
495
+ } catch (error) {
496
+ throw errorHandler.createError(
497
+ 'WUNDR_PLUGIN_UNLINK_FAILED',
498
+ 'Failed to unlink plugin',
499
+ { plugin },
500
+ true
501
+ );
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Test a plugin
507
+ */
508
+ private async testPlugin(plugin: string, options: any): Promise<void> {
509
+ try {
510
+ logger.info(`Testing plugin: ${chalk.cyan(plugin)}`);
511
+
512
+ await this.pluginManager.testPlugin(plugin, {
513
+ coverage: options.coverage,
514
+ });
515
+
516
+ logger.success(`Plugin ${plugin} tests passed`);
517
+ } catch (error) {
518
+ throw errorHandler.createError(
519
+ 'WUNDR_PLUGIN_TEST_FAILED',
520
+ 'Failed to test plugin',
521
+ { plugin, options },
522
+ true
523
+ );
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Set plugin configuration
529
+ */
530
+ private async setPluginConfig(
531
+ plugin: string,
532
+ key: string,
533
+ value: string
534
+ ): Promise<void> {
535
+ try {
536
+ await this.pluginManager.setPluginConfig(plugin, key, value);
537
+ logger.success(
538
+ `Plugin configuration updated: ${plugin}.${key} = ${value}`
539
+ );
540
+ } catch (error) {
541
+ throw errorHandler.createError(
542
+ 'WUNDR_PLUGIN_CONFIG_SET_FAILED',
543
+ 'Failed to set plugin configuration',
544
+ { plugin, key, value },
545
+ true
546
+ );
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Get plugin configuration
552
+ */
553
+ private async getPluginConfig(plugin: string, key?: string): Promise<void> {
554
+ try {
555
+ if (key) {
556
+ const value = await this.pluginManager.getPluginConfig(plugin, key);
557
+ console.log(`${key}: ${value}`);
558
+ } else {
559
+ const config = await this.pluginManager.getPluginConfig(plugin);
560
+ console.log(JSON.stringify(config, null, 2));
561
+ }
562
+ } catch (error) {
563
+ throw errorHandler.createError(
564
+ 'WUNDR_PLUGIN_CONFIG_GET_FAILED',
565
+ 'Failed to get plugin configuration',
566
+ { plugin, key },
567
+ true
568
+ );
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Helper methods for plugin operations
574
+ */
575
+ private async listInstalledPlugins(
576
+ enabledOnly: boolean = false
577
+ ): Promise<void> {
578
+ const plugins = await this.pluginManager.getInstalledPlugins();
579
+ const filteredPlugins = enabledOnly
580
+ ? plugins.filter(p => p.enabled)
581
+ : plugins;
582
+
583
+ if (filteredPlugins.length === 0) {
584
+ logger.info(
585
+ enabledOnly ? 'No enabled plugins found' : 'No plugins installed'
586
+ );
587
+ return;
588
+ }
589
+
590
+ logger.info(
591
+ `${enabledOnly ? 'Enabled plugins' : 'Installed plugins'} (${filteredPlugins.length}):`
592
+ );
593
+
594
+ const pluginData = filteredPlugins.map(plugin => ({
595
+ Name: plugin.name,
596
+ Version: plugin.version,
597
+ Status: plugin.enabled ? '✅ Enabled' : '❌ Disabled',
598
+ Commands: plugin.commands?.length || 0,
599
+ Description: plugin.description || 'No description',
600
+ }));
601
+
602
+ console.table(pluginData);
603
+ }
604
+
605
+ private async listAvailablePlugins(): Promise<void> {
606
+ const plugins = await this.pluginManager.getAvailablePlugins();
607
+
608
+ if (plugins.length === 0) {
609
+ logger.info('No plugins available in registry');
610
+ return;
611
+ }
612
+
613
+ logger.info(`Available plugins (${plugins.length}):`);
614
+
615
+ const pluginData = plugins.map(plugin => ({
616
+ Name: plugin.name,
617
+ Version: plugin.version,
618
+ Downloads: plugin.downloads || 0,
619
+ Updated: plugin.updated
620
+ ? new Date(plugin.updated).toLocaleDateString()
621
+ : 'Unknown',
622
+ Description: plugin.description || 'No description',
623
+ }));
624
+
625
+ console.table(pluginData);
626
+ }
627
+
628
+ private async createInteractivePlugin(name: string): Promise<any> {
629
+ const inquirer = await import('inquirer');
630
+
631
+ return await inquirer.default.prompt([
632
+ {
633
+ type: 'input',
634
+ name: 'description',
635
+ message: 'Plugin description:',
636
+ },
637
+ {
638
+ type: 'input',
639
+ name: 'author',
640
+ message: 'Author name:',
641
+ },
642
+ {
643
+ type: 'input',
644
+ name: 'version',
645
+ message: 'Initial version:',
646
+ default: '1.0.0',
647
+ },
648
+ {
649
+ type: 'checkbox',
650
+ name: 'features',
651
+ message: 'Plugin features:',
652
+ choices: [
653
+ 'Commands',
654
+ 'Hooks',
655
+ 'Configuration',
656
+ 'Templates',
657
+ 'Middleware',
658
+ ],
659
+ },
660
+ ]);
661
+ }
662
+
663
+ private async createPluginFromTemplate(
664
+ name: string,
665
+ template: string
666
+ ): Promise<any> {
667
+ const templates: Record<string, any> = {
668
+ basic: {
669
+ description: `A basic Wundr plugin: ${name}`,
670
+ features: ['Commands'],
671
+ },
672
+ advanced: {
673
+ description: `An advanced Wundr plugin: ${name}`,
674
+ features: ['Commands', 'Hooks', 'Configuration'],
675
+ },
676
+ template: {
677
+ description: `A template-based Wundr plugin: ${name}`,
678
+ features: ['Templates', 'Commands'],
679
+ },
680
+ };
681
+
682
+ return templates[template] || templates['basic'];
683
+ }
684
+
685
+ private async generatePluginStructure(
686
+ pluginPath: string,
687
+ config: any
688
+ ): Promise<void> {
689
+ await fs.ensureDir(pluginPath);
690
+
691
+ // Create package.json
692
+ const packageJson = {
693
+ name: path.basename(pluginPath),
694
+ version: config.version || '1.0.0',
695
+ description: config.description,
696
+ main: 'dist/index.js',
697
+ types: 'dist/index.d.ts',
698
+ files: ['dist'],
699
+ scripts: {
700
+ build: 'tsc',
701
+ dev: 'tsc --watch',
702
+ test: 'jest',
703
+ },
704
+ peerDependencies: {
705
+ '@wundr/cli': '^1.0.0',
706
+ },
707
+ devDependencies: {
708
+ typescript: '^5.0.0',
709
+ '@types/node': '^20.0.0',
710
+ jest: '^29.0.0',
711
+ },
712
+ wundr: {
713
+ plugin: true,
714
+ commands: config.features?.includes('Commands') || false,
715
+ hooks: config.features?.includes('Hooks') || false,
716
+ templates: config.features?.includes('Templates') || false,
717
+ },
718
+ };
719
+
720
+ await fs.writeJson(path.join(pluginPath, 'package.json'), packageJson, {
721
+ spaces: 2,
722
+ });
723
+
724
+ // Create TypeScript config
725
+ const tsConfig = {
726
+ compilerOptions: {
727
+ target: 'ES2020',
728
+ module: 'CommonJS',
729
+ outDir: './dist',
730
+ rootDir: './src',
731
+ strict: true,
732
+ esModuleInterop: true,
733
+ skipLibCheck: true,
734
+ forceConsistentCasingInFileNames: true,
735
+ declaration: true,
736
+ declarationMap: true,
737
+ sourceMap: true,
738
+ },
739
+ include: ['src/**/*'],
740
+ exclude: ['node_modules', 'dist'],
741
+ };
742
+
743
+ await fs.writeJson(path.join(pluginPath, 'tsconfig.json'), tsConfig, {
744
+ spaces: 2,
745
+ });
746
+
747
+ // Create source directory and files
748
+ const srcDir = path.join(pluginPath, 'src');
749
+ await fs.ensureDir(srcDir);
750
+
751
+ // Create main plugin file
752
+ const pluginCode = this.generatePluginCode(
753
+ path.basename(pluginPath),
754
+ config
755
+ );
756
+ await fs.writeFile(path.join(srcDir, 'index.ts'), pluginCode);
757
+
758
+ // Create README
759
+ const readme = this.generatePluginReadme(path.basename(pluginPath), config);
760
+ await fs.writeFile(path.join(pluginPath, 'README.md'), readme);
761
+ }
762
+
763
+ private generatePluginCode(name: string, config: any): string {
764
+ return `import { Plugin, PluginContext } from '@wundr/cli';
765
+
766
+ export default class ${this.toPascalCase(name)}Plugin implements Plugin {
767
+ name = '${name}';
768
+ version = '${config.version || '1.0.0'}';
769
+ description = '${config.description}';
770
+
771
+ async activate(context: PluginContext): Promise<void> {
772
+ // Plugin activation logic
773
+ context.logger.info('${name} plugin activated');
774
+
775
+ ${config.features?.includes('Commands') ? this.generateCommandCode(name) : ''}
776
+ ${config.features?.includes('Hooks') ? this.generateHookCode(name) : ''}
777
+ }
778
+
779
+ async deactivate(): Promise<void> {
780
+ // Plugin deactivation logic
781
+ }
782
+ }
783
+ `;
784
+ }
785
+
786
+ private generateCommandCode(name: string): string {
787
+ return `
788
+ // Register commands
789
+ context.registerCommand({
790
+ name: '${name}',
791
+ description: '${name} plugin command',
792
+ action: async (args, options, ctx) => {
793
+ ctx.logger.info('${name} command executed');
794
+ }
795
+ });
796
+ `;
797
+ }
798
+
799
+ private generateHookCode(name: string): string {
800
+ return `
801
+ // Register hooks
802
+ context.registerHook({
803
+ event: 'before-command',
804
+ handler: async (data, ctx) => {
805
+ ctx.logger.debug('${name} hook: before-command');
806
+ }
807
+ });
808
+ `;
809
+ }
810
+
811
+ private generatePluginReadme(name: string, config: any): string {
812
+ return `# ${name}
813
+
814
+ ${config.description}
815
+
816
+ ## Installation
817
+
818
+ \`\`\`bash
819
+ wundr plugin install ${name}
820
+ \`\`\`
821
+
822
+ ## Usage
823
+
824
+ \`\`\`bash
825
+ wundr ${name}
826
+ \`\`\`
827
+
828
+ ## Development
829
+
830
+ \`\`\`bash
831
+ npm install
832
+ npm run build
833
+ wundr plugin dev link .
834
+ \`\`\`
835
+
836
+ ## License
837
+
838
+ MIT
839
+ `;
840
+ }
841
+
842
+ private toPascalCase(str: string): string {
843
+ return str
844
+ .split(/[-_\s]/)
845
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
846
+ .join('');
847
+ }
848
+ }