@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,744 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { spawn, type ChildProcess } from 'child_process';
4
+ import chalk from 'chalk';
5
+ import { ConfigManager } from '../utils/config-manager';
6
+ import { logger } from '../utils/logger';
7
+ import { errorHandler } from '../utils/error-handler';
8
+ import { Plugin, PluginContext, PluginCommand, PluginHook } from '../types';
9
+
10
+ /**
11
+ * Plugin management system for CLI extensibility
12
+ */
13
+ export class PluginManager {
14
+ private loadedPlugins: Map<string, Plugin> = new Map();
15
+ private pluginCommands: Map<string, PluginCommand> = new Map();
16
+ private pluginHooks: Map<string, PluginHook[]> = new Map();
17
+ private pluginsDir: string;
18
+
19
+ constructor(private configManager: ConfigManager) {
20
+ this.pluginsDir = path.join(process.cwd(), '.wundr', 'plugins');
21
+ }
22
+
23
+ /**
24
+ * Initialize plugin system
25
+ */
26
+ async initialize(): Promise<void> {
27
+ try {
28
+ await fs.ensureDir(this.pluginsDir);
29
+ logger.debug('Plugin system initialized');
30
+ } catch (error) {
31
+ throw errorHandler.createError(
32
+ 'WUNDR_PLUGIN_INIT_FAILED',
33
+ 'Failed to initialize plugin system',
34
+ {},
35
+ true
36
+ );
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Load all plugins
42
+ */
43
+ async loadPlugins(): Promise<void> {
44
+ try {
45
+ const config = this.configManager.getConfig();
46
+ const enabledPlugins = config.plugins || [];
47
+
48
+ logger.debug(`Loading ${enabledPlugins.length} plugins...`);
49
+
50
+ for (const pluginName of enabledPlugins) {
51
+ try {
52
+ await this.loadPlugin(pluginName);
53
+ } catch (error) {
54
+ logger.warn(`Failed to load plugin ${pluginName}:`, error.message);
55
+ }
56
+ }
57
+
58
+ logger.debug(`Loaded ${this.loadedPlugins.size} plugins successfully`);
59
+ } catch (error) {
60
+ throw errorHandler.createError(
61
+ 'WUNDR_PLUGIN_LOAD_FAILED',
62
+ 'Failed to load plugins',
63
+ {},
64
+ true
65
+ );
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Load a specific plugin
71
+ */
72
+ async loadPlugin(pluginName: string): Promise<void> {
73
+ try {
74
+ if (this.loadedPlugins.has(pluginName)) {
75
+ logger.debug(`Plugin ${pluginName} already loaded`);
76
+ return;
77
+ }
78
+
79
+ const pluginPath = await this.getPluginPath(pluginName);
80
+ if (!(await fs.pathExists(pluginPath))) {
81
+ throw new Error(`Plugin not found: ${pluginName}`);
82
+ }
83
+
84
+ // Dynamic import of the plugin
85
+ const pluginModule = await this.importPlugin(pluginPath);
86
+ const plugin = this.createPluginInstance(pluginModule, pluginName);
87
+
88
+ // Create plugin context
89
+ const context = this.createPluginContext(pluginName);
90
+
91
+ // Activate the plugin
92
+ await plugin.activate(context);
93
+
94
+ // Store loaded plugin
95
+ this.loadedPlugins.set(pluginName, plugin);
96
+
97
+ logger.debug(`Plugin loaded: ${pluginName}`);
98
+ } catch (error) {
99
+ throw errorHandler.createError(
100
+ 'WUNDR_PLUGIN_LOAD_SINGLE_FAILED',
101
+ `Failed to load plugin: ${pluginName}`,
102
+ { pluginName },
103
+ true
104
+ );
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Unload a plugin
110
+ */
111
+ async unloadPlugin(pluginName: string): Promise<void> {
112
+ try {
113
+ const plugin = this.loadedPlugins.get(pluginName);
114
+ if (!plugin) {
115
+ logger.debug(`Plugin ${pluginName} is not loaded`);
116
+ return;
117
+ }
118
+
119
+ // Deactivate the plugin
120
+ await plugin.deactivate();
121
+
122
+ // Remove plugin commands and hooks
123
+ this.removePluginCommands(pluginName);
124
+ this.removePluginHooks(pluginName);
125
+
126
+ // Remove from loaded plugins
127
+ this.loadedPlugins.delete(pluginName);
128
+
129
+ logger.debug(`Plugin unloaded: ${pluginName}`);
130
+ } catch (error) {
131
+ throw errorHandler.createError(
132
+ 'WUNDR_PLUGIN_UNLOAD_FAILED',
133
+ `Failed to unload plugin: ${pluginName}`,
134
+ { pluginName },
135
+ true
136
+ );
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Install a plugin
142
+ */
143
+ async installPlugin(pluginName: string, options: any = {}): Promise<void> {
144
+ try {
145
+ logger.info(`Installing plugin: ${chalk.cyan(pluginName)}`);
146
+
147
+ // Determine installation method
148
+ if (this.isLocalPath(pluginName)) {
149
+ await this.installLocalPlugin(pluginName);
150
+ } else if (this.isGitUrl(pluginName)) {
151
+ await this.installGitPlugin(pluginName);
152
+ } else {
153
+ await this.installNpmPlugin(pluginName, options);
154
+ }
155
+
156
+ // Add to enabled plugins list
157
+ await this.addToEnabledPlugins(pluginName);
158
+
159
+ logger.success(`Plugin ${pluginName} installed successfully`);
160
+ } catch (error) {
161
+ throw errorHandler.createError(
162
+ 'WUNDR_PLUGIN_INSTALL_FAILED',
163
+ `Failed to install plugin: ${pluginName}`,
164
+ { pluginName, options },
165
+ true
166
+ );
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Uninstall a plugin
172
+ */
173
+ async uninstallPlugin(pluginName: string): Promise<void> {
174
+ try {
175
+ // Unload plugin if loaded
176
+ if (this.loadedPlugins.has(pluginName)) {
177
+ await this.unloadPlugin(pluginName);
178
+ }
179
+
180
+ // Remove plugin files
181
+ const pluginPath = await this.getPluginPath(pluginName);
182
+ if (await fs.pathExists(pluginPath)) {
183
+ await fs.remove(pluginPath);
184
+ }
185
+
186
+ // Remove from enabled plugins list
187
+ await this.removeFromEnabledPlugins(pluginName);
188
+
189
+ logger.success(`Plugin ${pluginName} uninstalled successfully`);
190
+ } catch (error) {
191
+ throw errorHandler.createError(
192
+ 'WUNDR_PLUGIN_UNINSTALL_FAILED',
193
+ `Failed to uninstall plugin: ${pluginName}`,
194
+ { pluginName },
195
+ true
196
+ );
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Enable a plugin
202
+ */
203
+ async enablePlugin(pluginName: string): Promise<void> {
204
+ try {
205
+ if (!(await this.isPluginInstalled(pluginName))) {
206
+ throw new Error(`Plugin not installed: ${pluginName}`);
207
+ }
208
+
209
+ await this.addToEnabledPlugins(pluginName);
210
+ await this.loadPlugin(pluginName);
211
+
212
+ logger.success(`Plugin ${pluginName} enabled`);
213
+ } catch (error) {
214
+ throw errorHandler.createError(
215
+ 'WUNDR_PLUGIN_ENABLE_FAILED',
216
+ `Failed to enable plugin: ${pluginName}`,
217
+ { pluginName },
218
+ true
219
+ );
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Disable a plugin
225
+ */
226
+ async disablePlugin(pluginName: string): Promise<void> {
227
+ try {
228
+ await this.unloadPlugin(pluginName);
229
+ await this.removeFromEnabledPlugins(pluginName);
230
+
231
+ logger.success(`Plugin ${pluginName} disabled`);
232
+ } catch (error) {
233
+ throw errorHandler.createError(
234
+ 'WUNDR_PLUGIN_DISABLE_FAILED',
235
+ `Failed to disable plugin: ${pluginName}`,
236
+ { pluginName },
237
+ true
238
+ );
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Get plugin information
244
+ */
245
+ async getPluginInfo(pluginName: string): Promise<any> {
246
+ try {
247
+ const pluginPath = await this.getPluginPath(pluginName);
248
+ const packageJsonPath = path.join(pluginPath, 'package.json');
249
+
250
+ if (!(await fs.pathExists(packageJsonPath))) {
251
+ return null;
252
+ }
253
+
254
+ const packageJson = await fs.readJson(packageJsonPath);
255
+ const plugin = this.loadedPlugins.get(pluginName);
256
+
257
+ return {
258
+ name: packageJson.name || pluginName,
259
+ version: packageJson.version,
260
+ description: packageJson.description,
261
+ author: packageJson.author,
262
+ enabled: !!plugin,
263
+ commands: plugin?.commands || [],
264
+ hooks: plugin?.hooks || [],
265
+ dependencies: packageJson.dependencies
266
+ ? Object.keys(packageJson.dependencies)
267
+ : [],
268
+ };
269
+ } catch (error) {
270
+ return null;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Check if plugin is installed
276
+ */
277
+ async isPluginInstalled(pluginName: string): Promise<boolean> {
278
+ const pluginPath = await this.getPluginPath(pluginName);
279
+ return await fs.pathExists(pluginPath);
280
+ }
281
+
282
+ /**
283
+ * Get installed plugins
284
+ */
285
+ async getInstalledPlugins(): Promise<any[]> {
286
+ try {
287
+ if (!(await fs.pathExists(this.pluginsDir))) {
288
+ return [];
289
+ }
290
+
291
+ const pluginDirs = await fs.readdir(this.pluginsDir);
292
+ const plugins: any[] = [];
293
+
294
+ for (const dir of pluginDirs) {
295
+ const pluginInfo = await this.getPluginInfo(dir);
296
+ if (pluginInfo) {
297
+ plugins.push(pluginInfo);
298
+ }
299
+ }
300
+
301
+ return plugins;
302
+ } catch (error) {
303
+ logger.debug('Failed to get installed plugins:', error);
304
+ return [];
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get available plugins from registry
310
+ */
311
+ async getAvailablePlugins(): Promise<any[]> {
312
+ try {
313
+ // This would query a plugin registry
314
+ // For now, return mock data
315
+ return [
316
+ {
317
+ name: '@wundr/plugin-git',
318
+ version: '1.0.0',
319
+ description: 'Git integration plugin',
320
+ downloads: 1000,
321
+ updated: new Date().toISOString(),
322
+ },
323
+ {
324
+ name: '@wundr/plugin-docker',
325
+ version: '1.2.0',
326
+ description: 'Docker integration plugin',
327
+ downloads: 800,
328
+ updated: new Date().toISOString(),
329
+ },
330
+ ];
331
+ } catch (error) {
332
+ logger.debug('Failed to get available plugins:', error);
333
+ return [];
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Search plugins
339
+ */
340
+ async searchPlugins(query: string, options: any = {}): Promise<any[]> {
341
+ try {
342
+ const available = await this.getAvailablePlugins();
343
+ return available
344
+ .filter(
345
+ plugin =>
346
+ plugin.name.toLowerCase().includes(query.toLowerCase()) ||
347
+ plugin.description.toLowerCase().includes(query.toLowerCase())
348
+ )
349
+ .slice(0, options.limit || 20);
350
+ } catch (error) {
351
+ logger.debug('Failed to search plugins:', error);
352
+ return [];
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Update plugin
358
+ */
359
+ async updatePlugin(pluginName: string): Promise<void> {
360
+ try {
361
+ logger.info(`Updating plugin: ${pluginName}`);
362
+
363
+ // This would check for updates and install newer version
364
+ // For now, just reload the plugin
365
+ await this.unloadPlugin(pluginName);
366
+ await this.loadPlugin(pluginName);
367
+
368
+ logger.success(`Plugin ${pluginName} updated`);
369
+ } catch (error) {
370
+ throw errorHandler.createError(
371
+ 'WUNDR_PLUGIN_UPDATE_FAILED',
372
+ `Failed to update plugin: ${pluginName}`,
373
+ { pluginName },
374
+ true
375
+ );
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Update all plugins
381
+ */
382
+ async updateAllPlugins(): Promise<void> {
383
+ const plugins = await this.getInstalledPlugins();
384
+
385
+ for (const plugin of plugins) {
386
+ try {
387
+ await this.updatePlugin(plugin.name);
388
+ } catch (error) {
389
+ logger.warn(`Failed to update plugin ${plugin.name}:`, error.message);
390
+ }
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Link plugin for development
396
+ */
397
+ async linkPlugin(pluginPath: string): Promise<void> {
398
+ try {
399
+ const absolutePath = path.resolve(pluginPath);
400
+ const packageJsonPath = path.join(absolutePath, 'package.json');
401
+
402
+ if (!(await fs.pathExists(packageJsonPath))) {
403
+ throw new Error('Invalid plugin: package.json not found');
404
+ }
405
+
406
+ const packageJson = await fs.readJson(packageJsonPath);
407
+ const pluginName = packageJson.name || path.basename(absolutePath);
408
+
409
+ // Create symlink to plugin
410
+ const linkPath = path.join(this.pluginsDir, pluginName);
411
+ await fs.ensureDir(path.dirname(linkPath));
412
+
413
+ if (await fs.pathExists(linkPath)) {
414
+ await fs.remove(linkPath);
415
+ }
416
+
417
+ await fs.symlink(absolutePath, linkPath);
418
+
419
+ logger.success(`Plugin linked: ${pluginName} -> ${absolutePath}`);
420
+ } catch (error) {
421
+ throw errorHandler.createError(
422
+ 'WUNDR_PLUGIN_LINK_FAILED',
423
+ 'Failed to link plugin',
424
+ { pluginPath },
425
+ true
426
+ );
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Unlink plugin
432
+ */
433
+ async unlinkPlugin(pluginName: string): Promise<void> {
434
+ try {
435
+ const pluginPath = path.join(this.pluginsDir, pluginName);
436
+
437
+ if (await fs.pathExists(pluginPath)) {
438
+ const stats = await fs.lstat(pluginPath);
439
+ if (stats.isSymbolicLink()) {
440
+ await fs.remove(pluginPath);
441
+ logger.success(`Plugin unlinked: ${pluginName}`);
442
+ } else {
443
+ throw new Error(`Plugin ${pluginName} is not a symlink`);
444
+ }
445
+ } else {
446
+ throw new Error(`Plugin ${pluginName} not found`);
447
+ }
448
+ } catch (error) {
449
+ throw errorHandler.createError(
450
+ 'WUNDR_PLUGIN_UNLINK_FAILED',
451
+ 'Failed to unlink plugin',
452
+ { pluginName },
453
+ true
454
+ );
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Test plugin
460
+ */
461
+ async testPlugin(pluginName: string, options: any = {}): Promise<void> {
462
+ try {
463
+ const pluginPath = await this.getPluginPath(pluginName);
464
+
465
+ const testCommand = options.coverage
466
+ ? 'npm run test:coverage'
467
+ : 'npm test';
468
+
469
+ await this.runCommand(testCommand, { cwd: pluginPath });
470
+ } catch (error) {
471
+ throw errorHandler.createError(
472
+ 'WUNDR_PLUGIN_TEST_FAILED',
473
+ `Failed to test plugin: ${pluginName}`,
474
+ { pluginName, options },
475
+ true
476
+ );
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Publish plugin
482
+ */
483
+ async publishPlugin(options: any = {}): Promise<void> {
484
+ try {
485
+ // This would publish to a plugin registry
486
+ logger.info('Publishing plugin to registry...');
487
+
488
+ // Mock implementation
489
+ await new Promise(resolve => setTimeout(resolve, 1000));
490
+
491
+ logger.success('Plugin published successfully');
492
+ } catch (error) {
493
+ throw errorHandler.createError(
494
+ 'WUNDR_PLUGIN_PUBLISH_FAILED',
495
+ 'Failed to publish plugin',
496
+ { options },
497
+ true
498
+ );
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Plugin configuration management
504
+ */
505
+ async setPluginConfig(
506
+ pluginName: string,
507
+ key: string,
508
+ value: string
509
+ ): Promise<void> {
510
+ this.configManager.set(`plugins.${pluginName}.${key}`, value);
511
+ await this.configManager.saveConfig();
512
+ }
513
+
514
+ async getPluginConfig(pluginName: string, key?: string): Promise<any> {
515
+ if (key) {
516
+ return this.configManager.get(`plugins.${pluginName}.${key}`);
517
+ } else {
518
+ return this.configManager.get(`plugins.${pluginName}`) || {};
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Plugin command and hook management
524
+ */
525
+ registerPluginCommand(pluginName: string, command: PluginCommand): void {
526
+ const commandKey = `${pluginName}:${command.name}`;
527
+ this.pluginCommands.set(commandKey, command);
528
+ logger.debug(`Plugin command registered: ${commandKey}`);
529
+ }
530
+
531
+ registerPluginHook(pluginName: string, hook: PluginHook): void {
532
+ if (!this.pluginHooks.has(hook.event)) {
533
+ this.pluginHooks.set(hook.event, []);
534
+ }
535
+
536
+ this.pluginHooks.get(hook.event)!.push(hook);
537
+ logger.debug(`Plugin hook registered: ${pluginName}:${hook.event}`);
538
+ }
539
+
540
+ async executePluginHooks(event: string, data: any): Promise<void> {
541
+ const hooks = this.pluginHooks.get(event) || [];
542
+
543
+ for (const hook of hooks) {
544
+ try {
545
+ await hook.handler(data, this.createHookContext());
546
+ } catch (error) {
547
+ logger.warn(`Plugin hook failed: ${event}`, error.message);
548
+ }
549
+ }
550
+ }
551
+
552
+ getPluginCommands(): Map<string, PluginCommand> {
553
+ return new Map(this.pluginCommands);
554
+ }
555
+
556
+ /**
557
+ * Helper methods
558
+ */
559
+ private async getPluginPath(pluginName: string): Promise<string> {
560
+ return path.join(this.pluginsDir, pluginName);
561
+ }
562
+
563
+ private async importPlugin(pluginPath: string): Promise<any> {
564
+ // In a real implementation, this would handle different module formats
565
+ const mainFile = path.join(pluginPath, 'index.js');
566
+
567
+ if (await fs.pathExists(mainFile)) {
568
+ return require(mainFile);
569
+ }
570
+
571
+ // Try package.json main field
572
+ const packageJsonPath = path.join(pluginPath, 'package.json');
573
+ if (await fs.pathExists(packageJsonPath)) {
574
+ const packageJson = await fs.readJson(packageJsonPath);
575
+ const main = packageJson.main || 'index.js';
576
+ return require(path.join(pluginPath, main));
577
+ }
578
+
579
+ throw new Error(`Plugin entry point not found: ${pluginPath}`);
580
+ }
581
+
582
+ private createPluginInstance(pluginModule: any, pluginName: string): Plugin {
583
+ // Handle different export formats
584
+ if (typeof pluginModule === 'function') {
585
+ return new pluginModule();
586
+ } else if (
587
+ pluginModule.default &&
588
+ typeof pluginModule.default === 'function'
589
+ ) {
590
+ return new pluginModule.default();
591
+ } else if (pluginModule.default) {
592
+ return pluginModule.default;
593
+ } else {
594
+ return pluginModule;
595
+ }
596
+ }
597
+
598
+ private createPluginContext(pluginName: string): PluginContext {
599
+ return {
600
+ config: this.configManager.getConfig(),
601
+ logger: logger,
602
+ registerCommand: (command: PluginCommand) => {
603
+ this.registerPluginCommand(pluginName, command);
604
+ },
605
+ registerHook: (hook: PluginHook) => {
606
+ this.registerPluginHook(pluginName, hook);
607
+ },
608
+ };
609
+ }
610
+
611
+ private createHookContext(): any {
612
+ return {
613
+ config: this.configManager.getConfig(),
614
+ logger: logger,
615
+ };
616
+ }
617
+
618
+ private removePluginCommands(pluginName: string): void {
619
+ for (const [key] of this.pluginCommands) {
620
+ if (key.startsWith(`${pluginName}:`)) {
621
+ this.pluginCommands.delete(key);
622
+ }
623
+ }
624
+ }
625
+
626
+ private removePluginHooks(pluginName: string): void {
627
+ for (const [event, hooks] of this.pluginHooks) {
628
+ const filteredHooks = hooks.filter(
629
+ hook => !hook.event.startsWith(pluginName)
630
+ );
631
+ this.pluginHooks.set(event, filteredHooks);
632
+ }
633
+ }
634
+
635
+ private async addToEnabledPlugins(pluginName: string): Promise<void> {
636
+ const config = this.configManager.getConfig();
637
+ if (!config.plugins.includes(pluginName)) {
638
+ config.plugins.push(pluginName);
639
+ await this.configManager.saveConfig();
640
+ }
641
+ }
642
+
643
+ private async removeFromEnabledPlugins(pluginName: string): Promise<void> {
644
+ const config = this.configManager.getConfig();
645
+ const index = config.plugins.indexOf(pluginName);
646
+ if (index > -1) {
647
+ config.plugins.splice(index, 1);
648
+ await this.configManager.saveConfig();
649
+ }
650
+ }
651
+
652
+ private isLocalPath(pluginName: string): boolean {
653
+ return (
654
+ pluginName.startsWith('./') ||
655
+ pluginName.startsWith('../') ||
656
+ path.isAbsolute(pluginName)
657
+ );
658
+ }
659
+
660
+ private isGitUrl(pluginName: string): boolean {
661
+ return (
662
+ pluginName.startsWith('git+') ||
663
+ pluginName.includes('github.com') ||
664
+ pluginName.includes('.git')
665
+ );
666
+ }
667
+
668
+ private async installLocalPlugin(pluginPath: string): Promise<void> {
669
+ const absolutePath = path.resolve(pluginPath);
670
+ const pluginName = path.basename(absolutePath);
671
+ const targetPath = path.join(this.pluginsDir, pluginName);
672
+
673
+ await fs.copy(absolutePath, targetPath);
674
+
675
+ // Install dependencies
676
+ await this.runCommand('npm install', { cwd: targetPath });
677
+ }
678
+
679
+ private async installGitPlugin(gitUrl: string): Promise<void> {
680
+ const pluginName = path.basename(gitUrl, '.git');
681
+ const targetPath = path.join(this.pluginsDir, pluginName);
682
+
683
+ await this.runCommand(`git clone ${gitUrl} ${targetPath}`);
684
+ await this.runCommand('npm install', { cwd: targetPath });
685
+ }
686
+
687
+ private async installNpmPlugin(
688
+ pluginName: string,
689
+ options: any
690
+ ): Promise<void> {
691
+ const versionSpec = options.version ? `@${options.version}` : '';
692
+ const targetPath = path.join(this.pluginsDir, pluginName);
693
+
694
+ await fs.ensureDir(targetPath);
695
+
696
+ // Create temporary package.json
697
+ const tempPackageJson = {
698
+ name: 'temp-plugin-installer',
699
+ version: '1.0.0',
700
+ dependencies: {
701
+ [pluginName]: versionSpec || 'latest',
702
+ },
703
+ };
704
+
705
+ await fs.writeJson(path.join(targetPath, 'package.json'), tempPackageJson);
706
+ await this.runCommand('npm install', { cwd: targetPath });
707
+
708
+ // Move plugin to correct location
709
+ const installedPath = path.join(targetPath, 'node_modules', pluginName);
710
+ const finalPath = path.join(this.pluginsDir, pluginName);
711
+
712
+ if (finalPath !== targetPath) {
713
+ await fs.move(installedPath, finalPath);
714
+ await fs.remove(targetPath);
715
+ }
716
+ }
717
+
718
+ private async runCommand(command: string, options: any = {}): Promise<void> {
719
+ return new Promise((resolve, reject) => {
720
+ const [cmd, ...args] = command.split(' ');
721
+ if (!cmd) {
722
+ reject(new Error('Invalid command: empty command string'));
723
+ return;
724
+ }
725
+ const child: ChildProcess = spawn(cmd, args, {
726
+ stdio: 'inherit',
727
+ shell: true,
728
+ ...options,
729
+ });
730
+
731
+ child.on('exit', code => {
732
+ if (code === 0) {
733
+ resolve();
734
+ } else {
735
+ reject(
736
+ new Error(`Command failed with exit code ${code}: ${command}`)
737
+ );
738
+ }
739
+ });
740
+
741
+ child.on('error', reject);
742
+ });
743
+ }
744
+ }