@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.
- package/README.md +551 -0
- package/bin/wundr.js +39 -0
- package/dist/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +339 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +612 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +735 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +437 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +71 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +738 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +71 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +674 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +587 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +32 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +570 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup-commands.d.ts +39 -0
- package/dist/commands/computer-setup-commands.d.ts.map +1 -0
- package/dist/commands/computer-setup-commands.js +563 -0
- package/dist/commands/computer-setup-commands.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +7 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +481 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +537 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +480 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +584 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +649 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +399 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +610 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +682 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +730 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +623 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +416 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +739 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +120 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +595 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +119 -0
- package/src/ai/ai-service.ts +595 -0
- package/src/ai/claude-client.ts +490 -0
- package/src/ai/conversation-manager.ts +907 -0
- package/src/ai/index.ts +8 -0
- package/src/cli.ts +202 -0
- package/src/commands/ai.ts +995 -0
- package/src/commands/analyze-optimized.ts +641 -0
- package/src/commands/analyze.ts +576 -0
- package/src/commands/batch.ts +935 -0
- package/src/commands/chat.ts +876 -0
- package/src/commands/claude-init.ts +715 -0
- package/src/commands/claude-setup.ts +697 -0
- package/src/commands/computer-setup-commands.ts +709 -0
- package/src/commands/computer-setup.ts +565 -0
- package/src/commands/create-command.ts +175 -0
- package/src/commands/create.ts +727 -0
- package/src/commands/dashboard.ts +691 -0
- package/src/commands/govern.ts +635 -0
- package/src/commands/init.ts +677 -0
- package/src/commands/performance-optimizer.ts +864 -0
- package/src/commands/plugins.ts +848 -0
- package/src/commands/setup.ts +508 -0
- package/src/commands/test-init.ts +242 -0
- package/src/commands/test.ts +264 -0
- package/src/commands/watch.ts +755 -0
- package/src/context/context-manager.ts +546 -0
- package/src/context/index.ts +9 -0
- package/src/context/session-manager.ts +1019 -0
- package/src/index.ts +64 -0
- package/src/interactive/interactive-mode.ts +830 -0
- package/src/nlp/command-mapper.ts +885 -0
- package/src/nlp/command-parser.ts +564 -0
- package/src/nlp/index.ts +4 -0
- package/src/nlp/intent-classifier.ts +458 -0
- package/src/nlp/intent-parser.ts +1101 -0
- package/src/plugins/plugin-manager.ts +744 -0
- package/src/types/index.ts +252 -0
- package/src/types/modules.d.ts +56 -0
- package/src/utils/config-manager.ts +391 -0
- package/src/utils/error-handler.ts +192 -0
- package/src/utils/logger.ts +104 -0
- package/templates/batch/ci-cd.yaml +62 -0
- package/templates/component/{{fileName}}.test.tsx +17 -0
- package/templates/component/{{fileName}}.tsx +21 -0
- package/templates/service/{{fileName}}.ts +98 -0
- package/templates/wundr-test.config.js +0 -0
- package/test-suites/api/health.spec.ts +134 -0
- package/test-suites/helpers/test-config.ts +84 -0
- package/test-suites/ui/accessibility.spec.ts +102 -0
- 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
|
+
}
|