@ruvector/edge-net 0.4.6 → 0.5.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/package.json +13 -2
- package/plugins/cli.js +395 -0
- package/plugins/implementations/compression.js +132 -0
- package/plugins/implementations/e2e-encryption.js +160 -0
- package/plugins/implementations/federated-learning.js +249 -0
- package/plugins/implementations/reputation-staking.js +243 -0
- package/plugins/implementations/swarm-intelligence.js +386 -0
- package/plugins/index.js +90 -0
- package/plugins/plugin-loader.js +440 -0
- package/plugins/plugin-manifest.js +702 -0
- package/plugins/plugin-sdk.js +496 -0
- package/tests/plugin-system-test.js +382 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Intelligence Plugin
|
|
3
|
+
*
|
|
4
|
+
* Distributed optimization using bio-inspired algorithms:
|
|
5
|
+
* - Particle Swarm Optimization (PSO)
|
|
6
|
+
* - Ant Colony Optimization (ACO)
|
|
7
|
+
* - Genetic Algorithm (GA)
|
|
8
|
+
* - Differential Evolution (DE)
|
|
9
|
+
*
|
|
10
|
+
* @module @ruvector/edge-net/plugins/swarm-intelligence
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EventEmitter } from 'events';
|
|
14
|
+
|
|
15
|
+
export class SwarmIntelligencePlugin extends EventEmitter {
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
this.config = {
|
|
20
|
+
algorithm: config.algorithm || 'pso',
|
|
21
|
+
populationSize: config.populationSize || 50,
|
|
22
|
+
iterations: config.iterations || 100,
|
|
23
|
+
dimensions: config.dimensions || 10,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.swarms = new Map(); // swarmId -> SwarmState
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create optimization swarm
|
|
31
|
+
*/
|
|
32
|
+
createSwarm(swarmId, options = {}) {
|
|
33
|
+
const swarm = {
|
|
34
|
+
id: swarmId,
|
|
35
|
+
algorithm: options.algorithm || this.config.algorithm,
|
|
36
|
+
dimensions: options.dimensions || this.config.dimensions,
|
|
37
|
+
bounds: options.bounds || { min: -10, max: 10 },
|
|
38
|
+
fitnessFunction: options.fitnessFunction || this._defaultFitness,
|
|
39
|
+
particles: [],
|
|
40
|
+
globalBest: null,
|
|
41
|
+
globalBestFitness: Infinity,
|
|
42
|
+
iteration: 0,
|
|
43
|
+
status: 'initialized',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Initialize particles
|
|
47
|
+
for (let i = 0; i < this.config.populationSize; i++) {
|
|
48
|
+
swarm.particles.push(this._createParticle(swarm));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.swarms.set(swarmId, swarm);
|
|
52
|
+
this.emit('swarm:created', { swarmId, algorithm: swarm.algorithm });
|
|
53
|
+
|
|
54
|
+
return swarm;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create single particle
|
|
59
|
+
*/
|
|
60
|
+
_createParticle(swarm) {
|
|
61
|
+
const position = Array(swarm.dimensions).fill(0).map(() =>
|
|
62
|
+
swarm.bounds.min + Math.random() * (swarm.bounds.max - swarm.bounds.min)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
position,
|
|
67
|
+
velocity: Array(swarm.dimensions).fill(0).map(() => (Math.random() - 0.5) * 2),
|
|
68
|
+
bestPosition: [...position],
|
|
69
|
+
bestFitness: Infinity,
|
|
70
|
+
fitness: Infinity,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Default fitness function (minimize sum of squares)
|
|
76
|
+
*/
|
|
77
|
+
_defaultFitness(position) {
|
|
78
|
+
return position.reduce((sum, x) => sum + x * x, 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Run optimization step
|
|
83
|
+
*/
|
|
84
|
+
step(swarmId) {
|
|
85
|
+
const swarm = this.swarms.get(swarmId);
|
|
86
|
+
if (!swarm) throw new Error(`Swarm not found: ${swarmId}`);
|
|
87
|
+
|
|
88
|
+
swarm.status = 'running';
|
|
89
|
+
swarm.iteration++;
|
|
90
|
+
|
|
91
|
+
switch (swarm.algorithm) {
|
|
92
|
+
case 'pso':
|
|
93
|
+
this._psoStep(swarm);
|
|
94
|
+
break;
|
|
95
|
+
case 'ga':
|
|
96
|
+
this._gaStep(swarm);
|
|
97
|
+
break;
|
|
98
|
+
case 'de':
|
|
99
|
+
this._deStep(swarm);
|
|
100
|
+
break;
|
|
101
|
+
case 'aco':
|
|
102
|
+
this._acoStep(swarm);
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
this._psoStep(swarm);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.emit('swarm:step', {
|
|
109
|
+
swarmId,
|
|
110
|
+
iteration: swarm.iteration,
|
|
111
|
+
bestFitness: swarm.globalBestFitness,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
iteration: swarm.iteration,
|
|
116
|
+
bestFitness: swarm.globalBestFitness,
|
|
117
|
+
bestPosition: swarm.globalBest,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Particle Swarm Optimization step
|
|
123
|
+
*/
|
|
124
|
+
_psoStep(swarm) {
|
|
125
|
+
const w = 0.7; // Inertia weight
|
|
126
|
+
const c1 = 1.5; // Cognitive coefficient
|
|
127
|
+
const c2 = 1.5; // Social coefficient
|
|
128
|
+
|
|
129
|
+
for (const particle of swarm.particles) {
|
|
130
|
+
// Update velocity
|
|
131
|
+
for (let d = 0; d < swarm.dimensions; d++) {
|
|
132
|
+
const r1 = Math.random();
|
|
133
|
+
const r2 = Math.random();
|
|
134
|
+
|
|
135
|
+
particle.velocity[d] =
|
|
136
|
+
w * particle.velocity[d] +
|
|
137
|
+
c1 * r1 * (particle.bestPosition[d] - particle.position[d]) +
|
|
138
|
+
c2 * r2 * ((swarm.globalBest?.[d] || 0) - particle.position[d]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update position
|
|
142
|
+
for (let d = 0; d < swarm.dimensions; d++) {
|
|
143
|
+
particle.position[d] += particle.velocity[d];
|
|
144
|
+
// Clamp to bounds
|
|
145
|
+
particle.position[d] = Math.max(swarm.bounds.min,
|
|
146
|
+
Math.min(swarm.bounds.max, particle.position[d]));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Evaluate fitness
|
|
150
|
+
particle.fitness = swarm.fitnessFunction(particle.position);
|
|
151
|
+
|
|
152
|
+
// Update personal best
|
|
153
|
+
if (particle.fitness < particle.bestFitness) {
|
|
154
|
+
particle.bestFitness = particle.fitness;
|
|
155
|
+
particle.bestPosition = [...particle.position];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update global best
|
|
159
|
+
if (particle.fitness < swarm.globalBestFitness) {
|
|
160
|
+
swarm.globalBestFitness = particle.fitness;
|
|
161
|
+
swarm.globalBest = [...particle.position];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Genetic Algorithm step
|
|
168
|
+
*/
|
|
169
|
+
_gaStep(swarm) {
|
|
170
|
+
const mutationRate = 0.1;
|
|
171
|
+
const crossoverRate = 0.8;
|
|
172
|
+
|
|
173
|
+
// Evaluate all
|
|
174
|
+
for (const particle of swarm.particles) {
|
|
175
|
+
particle.fitness = swarm.fitnessFunction(particle.position);
|
|
176
|
+
if (particle.fitness < swarm.globalBestFitness) {
|
|
177
|
+
swarm.globalBestFitness = particle.fitness;
|
|
178
|
+
swarm.globalBest = [...particle.position];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Selection (tournament)
|
|
183
|
+
const selected = [];
|
|
184
|
+
for (let i = 0; i < swarm.particles.length; i++) {
|
|
185
|
+
const a = swarm.particles[Math.floor(Math.random() * swarm.particles.length)];
|
|
186
|
+
const b = swarm.particles[Math.floor(Math.random() * swarm.particles.length)];
|
|
187
|
+
selected.push(a.fitness < b.fitness ? a : b);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Crossover and mutation
|
|
191
|
+
for (let i = 0; i < swarm.particles.length; i += 2) {
|
|
192
|
+
const p1 = selected[i];
|
|
193
|
+
const p2 = selected[i + 1] || selected[0];
|
|
194
|
+
|
|
195
|
+
if (Math.random() < crossoverRate && swarm.particles[i + 1]) {
|
|
196
|
+
const crossPoint = Math.floor(Math.random() * swarm.dimensions);
|
|
197
|
+
for (let d = crossPoint; d < swarm.dimensions; d++) {
|
|
198
|
+
const temp = swarm.particles[i].position[d];
|
|
199
|
+
swarm.particles[i].position[d] = p2.position[d];
|
|
200
|
+
swarm.particles[i + 1].position[d] = temp;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Mutation
|
|
205
|
+
for (const particle of [swarm.particles[i], swarm.particles[i + 1]]) {
|
|
206
|
+
if (!particle) continue;
|
|
207
|
+
for (let d = 0; d < swarm.dimensions; d++) {
|
|
208
|
+
if (Math.random() < mutationRate) {
|
|
209
|
+
particle.position[d] += (Math.random() - 0.5) *
|
|
210
|
+
(swarm.bounds.max - swarm.bounds.min) * 0.1;
|
|
211
|
+
particle.position[d] = Math.max(swarm.bounds.min,
|
|
212
|
+
Math.min(swarm.bounds.max, particle.position[d]));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Differential Evolution step
|
|
221
|
+
*/
|
|
222
|
+
_deStep(swarm) {
|
|
223
|
+
const F = 0.8; // Mutation factor
|
|
224
|
+
const CR = 0.9; // Crossover rate
|
|
225
|
+
|
|
226
|
+
const newPositions = [];
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < swarm.particles.length; i++) {
|
|
229
|
+
// Select 3 random distinct particles
|
|
230
|
+
const indices = [];
|
|
231
|
+
while (indices.length < 3) {
|
|
232
|
+
const idx = Math.floor(Math.random() * swarm.particles.length);
|
|
233
|
+
if (idx !== i && !indices.includes(idx)) indices.push(idx);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const [a, b, c] = indices.map(idx => swarm.particles[idx]);
|
|
237
|
+
|
|
238
|
+
// Create mutant
|
|
239
|
+
const mutant = a.position.map((v, d) =>
|
|
240
|
+
v + F * (b.position[d] - c.position[d])
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Crossover
|
|
244
|
+
const trial = swarm.particles[i].position.map((v, d) =>
|
|
245
|
+
Math.random() < CR ? mutant[d] : v
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Clamp
|
|
249
|
+
trial.forEach((v, d) => {
|
|
250
|
+
trial[d] = Math.max(swarm.bounds.min, Math.min(swarm.bounds.max, v));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const trialFitness = swarm.fitnessFunction(trial);
|
|
254
|
+
|
|
255
|
+
if (trialFitness < swarm.particles[i].fitness) {
|
|
256
|
+
newPositions.push({ idx: i, position: trial, fitness: trialFitness });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Apply updates
|
|
261
|
+
for (const { idx, position, fitness } of newPositions) {
|
|
262
|
+
swarm.particles[idx].position = position;
|
|
263
|
+
swarm.particles[idx].fitness = fitness;
|
|
264
|
+
|
|
265
|
+
if (fitness < swarm.globalBestFitness) {
|
|
266
|
+
swarm.globalBestFitness = fitness;
|
|
267
|
+
swarm.globalBest = [...position];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Ant Colony Optimization step (simplified for continuous optimization)
|
|
274
|
+
*/
|
|
275
|
+
_acoStep(swarm) {
|
|
276
|
+
const evaporationRate = 0.1;
|
|
277
|
+
const pheromoneDeposit = 1.0;
|
|
278
|
+
|
|
279
|
+
// Use pheromone as bias toward best solutions
|
|
280
|
+
if (!swarm.pheromone) {
|
|
281
|
+
swarm.pheromone = Array(swarm.dimensions).fill(0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const particle of swarm.particles) {
|
|
285
|
+
// Move influenced by pheromone
|
|
286
|
+
for (let d = 0; d < swarm.dimensions; d++) {
|
|
287
|
+
const bias = swarm.pheromone[d] * 0.1;
|
|
288
|
+
particle.position[d] += (Math.random() - 0.5) * 2 + bias;
|
|
289
|
+
particle.position[d] = Math.max(swarm.bounds.min,
|
|
290
|
+
Math.min(swarm.bounds.max, particle.position[d]));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
particle.fitness = swarm.fitnessFunction(particle.position);
|
|
294
|
+
|
|
295
|
+
if (particle.fitness < swarm.globalBestFitness) {
|
|
296
|
+
swarm.globalBestFitness = particle.fitness;
|
|
297
|
+
swarm.globalBest = [...particle.position];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Update pheromone
|
|
302
|
+
for (let d = 0; d < swarm.dimensions; d++) {
|
|
303
|
+
swarm.pheromone[d] *= (1 - evaporationRate);
|
|
304
|
+
if (swarm.globalBest) {
|
|
305
|
+
swarm.pheromone[d] += pheromoneDeposit / (1 + Math.abs(swarm.globalBest[d]));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Run full optimization
|
|
312
|
+
*/
|
|
313
|
+
async optimize(swarmId, options = {}) {
|
|
314
|
+
const maxIterations = options.iterations || this.config.iterations;
|
|
315
|
+
const targetFitness = options.targetFitness ?? -Infinity;
|
|
316
|
+
|
|
317
|
+
const swarm = this.swarms.get(swarmId);
|
|
318
|
+
if (!swarm) throw new Error(`Swarm not found: ${swarmId}`);
|
|
319
|
+
|
|
320
|
+
while (swarm.iteration < maxIterations && swarm.globalBestFitness > targetFitness) {
|
|
321
|
+
this.step(swarmId);
|
|
322
|
+
|
|
323
|
+
// Yield for async operation
|
|
324
|
+
if (swarm.iteration % 10 === 0) {
|
|
325
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
swarm.status = 'completed';
|
|
330
|
+
|
|
331
|
+
this.emit('swarm:completed', {
|
|
332
|
+
swarmId,
|
|
333
|
+
iterations: swarm.iteration,
|
|
334
|
+
bestFitness: swarm.globalBestFitness,
|
|
335
|
+
bestPosition: swarm.globalBest,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
iterations: swarm.iteration,
|
|
340
|
+
bestFitness: swarm.globalBestFitness,
|
|
341
|
+
bestPosition: swarm.globalBest,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Distribute optimization across network nodes
|
|
347
|
+
*/
|
|
348
|
+
async distributeOptimization(swarmId, nodes, network) {
|
|
349
|
+
const swarm = this.swarms.get(swarmId);
|
|
350
|
+
if (!swarm) throw new Error(`Swarm not found: ${swarmId}`);
|
|
351
|
+
|
|
352
|
+
// Split particles among nodes
|
|
353
|
+
const particlesPerNode = Math.ceil(swarm.particles.length / nodes.length);
|
|
354
|
+
const tasks = [];
|
|
355
|
+
|
|
356
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
357
|
+
const startIdx = i * particlesPerNode;
|
|
358
|
+
const endIdx = Math.min(startIdx + particlesPerNode, swarm.particles.length);
|
|
359
|
+
const nodeParticles = swarm.particles.slice(startIdx, endIdx);
|
|
360
|
+
|
|
361
|
+
tasks.push({
|
|
362
|
+
nodeId: nodes[i],
|
|
363
|
+
particles: nodeParticles.map(p => ({ position: p.position })),
|
|
364
|
+
globalBest: swarm.globalBest,
|
|
365
|
+
iteration: swarm.iteration,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
this.emit('distributed', { swarmId, nodeCount: nodes.length });
|
|
370
|
+
|
|
371
|
+
return tasks;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
getSwarm(swarmId) {
|
|
375
|
+
return this.swarms.get(swarmId);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
getStats() {
|
|
379
|
+
return {
|
|
380
|
+
activeSwarms: this.swarms.size,
|
|
381
|
+
config: this.config,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export default SwarmIntelligencePlugin;
|
package/plugins/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-Net Plugin System
|
|
3
|
+
*
|
|
4
|
+
* Secure, modular plugin architecture for extending edge-net functionality.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Official plugin catalog (practical to exotic)
|
|
8
|
+
* - Custom plugin SDK for user-created plugins
|
|
9
|
+
* - Capability-based security sandboxing
|
|
10
|
+
* - Lazy loading with verification
|
|
11
|
+
* - Plugin bundles for common use cases
|
|
12
|
+
*
|
|
13
|
+
* @module @ruvector/edge-net/plugins
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```javascript
|
|
17
|
+
* // Load official plugins
|
|
18
|
+
* import { PluginManager, PLUGIN_CATALOG, PLUGIN_BUNDLES } from '@ruvector/edge-net/plugins';
|
|
19
|
+
*
|
|
20
|
+
* const plugins = PluginManager.getInstance();
|
|
21
|
+
*
|
|
22
|
+
* // View available plugins
|
|
23
|
+
* console.log(plugins.getCatalog());
|
|
24
|
+
*
|
|
25
|
+
* // Load a bundle
|
|
26
|
+
* await plugins.loadBundle('privacy-focused');
|
|
27
|
+
*
|
|
28
|
+
* // Load individual plugin
|
|
29
|
+
* const encryption = await plugins.load('privacy.e2e-encryption');
|
|
30
|
+
*
|
|
31
|
+
* // Create custom plugin
|
|
32
|
+
* import { BasePlugin, generatePluginTemplate, getRegistry } from '@ruvector/edge-net/plugins/sdk';
|
|
33
|
+
*
|
|
34
|
+
* class MyPlugin extends BasePlugin {
|
|
35
|
+
* static manifest = { ... };
|
|
36
|
+
* async onInit() { ... }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* getRegistry().register(MyPlugin);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// Core exports
|
|
44
|
+
export { PLUGIN_CATALOG, PLUGIN_BUNDLES, PluginCategory, PluginTier, Capability } from './plugin-manifest.js';
|
|
45
|
+
export { PluginLoader, PluginManager } from './plugin-loader.js';
|
|
46
|
+
export {
|
|
47
|
+
BasePlugin,
|
|
48
|
+
validateManifest,
|
|
49
|
+
validatePlugin,
|
|
50
|
+
PluginRegistry,
|
|
51
|
+
generatePluginTemplate,
|
|
52
|
+
getRegistry,
|
|
53
|
+
} from './plugin-sdk.js';
|
|
54
|
+
|
|
55
|
+
// Implementation exports (for direct use)
|
|
56
|
+
export { CompressionPlugin } from './implementations/compression.js';
|
|
57
|
+
export { E2EEncryptionPlugin } from './implementations/e2e-encryption.js';
|
|
58
|
+
export { FederatedLearningPlugin } from './implementations/federated-learning.js';
|
|
59
|
+
export { ReputationStakingPlugin } from './implementations/reputation-staking.js';
|
|
60
|
+
export { SwarmIntelligencePlugin } from './implementations/swarm-intelligence.js';
|
|
61
|
+
|
|
62
|
+
// Convenience function to get started quickly
|
|
63
|
+
import { PluginManager } from './plugin-loader.js';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Quick start - get plugin manager with standard bundle
|
|
67
|
+
*/
|
|
68
|
+
export async function initPlugins(options = {}) {
|
|
69
|
+
const manager = PluginManager.getInstance(options);
|
|
70
|
+
|
|
71
|
+
if (options.bundle) {
|
|
72
|
+
await manager.loadBundle(options.bundle);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return manager;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* List all available plugins with their status
|
|
80
|
+
*/
|
|
81
|
+
export function listPlugins() {
|
|
82
|
+
const manager = PluginManager.getInstance();
|
|
83
|
+
return manager.getCatalog();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
initPlugins,
|
|
88
|
+
listPlugins,
|
|
89
|
+
PluginManager,
|
|
90
|
+
};
|