spiralcord-full 2.2.7 → 2.3.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/bin/spiral.js +360 -9
- package/package.json +1 -1
- package/src/plugins/manager.js +15 -0
- package/src/types/index.d.ts +26 -0
package/bin/spiral.js
CHANGED
|
@@ -154,18 +154,364 @@ async function startBot(config) {
|
|
|
154
154
|
program
|
|
155
155
|
.command('run')
|
|
156
156
|
.description('Start the bot runtime')
|
|
157
|
-
.
|
|
157
|
+
.option('-R, --realtime', 'Start with auto-reload and REPL console')
|
|
158
|
+
.action(async (options) => {
|
|
158
159
|
try {
|
|
159
160
|
const config = await loadConfig();
|
|
160
161
|
if (!config) return;
|
|
161
|
-
const runtime = await startBot(config);
|
|
162
|
-
console.log('Bot is running! Press Ctrl+C to stop.');
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
if (options.realtime) {
|
|
164
|
+
const chokidar = (() => {
|
|
165
|
+
try {
|
|
166
|
+
const resolved = require.resolve('chokidar', { paths: [process.cwd()] });
|
|
167
|
+
return require(resolved);
|
|
168
|
+
} catch { return null; }
|
|
169
|
+
})();
|
|
170
|
+
if (!chokidar) {
|
|
171
|
+
console.error('Error: "spiral run -R" requires chokidar. Run: npm install chokidar');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let runtime = null;
|
|
176
|
+
let restarting = false;
|
|
177
|
+
let pendingRestart = false;
|
|
178
|
+
let r = null;
|
|
179
|
+
|
|
180
|
+
async function start() {
|
|
181
|
+
const cfg = await loadConfig();
|
|
182
|
+
if (!cfg) return;
|
|
183
|
+
runtime = await startBot(cfg);
|
|
184
|
+
console.log('[runtime] Ready. Press Ctrl+C to stop.');
|
|
185
|
+
startRepl();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function startRepl() {
|
|
189
|
+
if (r) r.close();
|
|
190
|
+
const repl = require('repl');
|
|
191
|
+
r = repl.start({
|
|
192
|
+
prompt: 'spiral> ',
|
|
193
|
+
useGlobal: false,
|
|
194
|
+
terminal: true
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
r.context.runtime = runtime;
|
|
198
|
+
r.context.client = runtime.client;
|
|
199
|
+
r.context.db = runtime.db;
|
|
200
|
+
r.context.manager = runtime.pluginManager;
|
|
201
|
+
r.context.config = runtime.config;
|
|
202
|
+
|
|
203
|
+
const spmCmds = (() => {
|
|
204
|
+
try {
|
|
205
|
+
return require('../spm/lib/commands');
|
|
206
|
+
} catch { return null; }
|
|
207
|
+
})();
|
|
208
|
+
|
|
209
|
+
const pluginCmds = runtime.pluginManager.getReplCommands();
|
|
210
|
+
const cmdCount = Object.keys(pluginCmds).length;
|
|
211
|
+
if (cmdCount > 0) {
|
|
212
|
+
console.log(`[repl] ${cmdCount} plugin command(s) registered: ${Object.keys(pluginCmds).map(c => `.${c}`).join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
r.defineCommand('status', {
|
|
216
|
+
help: 'Show uptime, servers, users',
|
|
217
|
+
action: function () {
|
|
218
|
+
this.clearBufferedCommand();
|
|
219
|
+
const uptime = process.uptime();
|
|
220
|
+
const h = Math.floor(uptime / 3600);
|
|
221
|
+
const m = Math.floor((uptime % 3600) / 60);
|
|
222
|
+
const s = Math.floor(uptime % 60);
|
|
223
|
+
const servers = runtime.client.guilds.cache.size;
|
|
224
|
+
const users = runtime.client.guilds.cache.reduce((acc, g) => acc + g.memberCount, 0);
|
|
225
|
+
console.log(`Uptime: ${h}h ${m}m ${s}s | Servers: ${servers} | Users: ${users}`);
|
|
226
|
+
this.displayPrompt();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
r.defineCommand('reload', {
|
|
231
|
+
help: 'Hot-reload all plugins',
|
|
232
|
+
action: function () {
|
|
233
|
+
this.clearBufferedCommand();
|
|
234
|
+
(async () => {
|
|
235
|
+
try {
|
|
236
|
+
await runtime.stop();
|
|
237
|
+
clearSpiralCache();
|
|
238
|
+
await start();
|
|
239
|
+
console.log('Plugins reloaded.');
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.error('Reload failed:', err.message);
|
|
242
|
+
}
|
|
243
|
+
r.displayPrompt();
|
|
244
|
+
})();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
r.defineCommand('stop', {
|
|
249
|
+
help: 'Stop the bot',
|
|
250
|
+
action: function () {
|
|
251
|
+
this.clearBufferedCommand();
|
|
252
|
+
(async () => {
|
|
253
|
+
console.log('Shutting down...');
|
|
254
|
+
if (runtime) await runtime.stop();
|
|
255
|
+
process.exit(0);
|
|
256
|
+
})();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
r.defineCommand('commands', {
|
|
261
|
+
help: 'List all registered commands',
|
|
262
|
+
action: function () {
|
|
263
|
+
this.clearBufferedCommand();
|
|
264
|
+
const cmds = runtime.pluginManager.getAllCommands();
|
|
265
|
+
if (cmds.size === 0) {
|
|
266
|
+
console.log('No commands registered.');
|
|
267
|
+
} else {
|
|
268
|
+
for (const [name, cmd] of cmds) {
|
|
269
|
+
console.log(` ${cmd.slash ? '/' : '.'}${name} — ${cmd.description || ''}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
r.displayPrompt();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
r.defineCommand('plugins', {
|
|
277
|
+
help: 'List installed plugins',
|
|
278
|
+
action: function () {
|
|
279
|
+
this.clearBufferedCommand();
|
|
280
|
+
(async () => {
|
|
281
|
+
const result = await spmCmds.listPlugins();
|
|
282
|
+
if (result.message) process.stdout.write(result.message);
|
|
283
|
+
r.displayPrompt();
|
|
284
|
+
})();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
r.defineCommand('install', {
|
|
289
|
+
help: 'Install a plugin from GitHub: spm.install <user/repo>',
|
|
290
|
+
action: function (source) {
|
|
291
|
+
this.clearBufferedCommand();
|
|
292
|
+
(async () => {
|
|
293
|
+
if (!source.trim()) {
|
|
294
|
+
console.log('Usage: spm.install <user/repo>');
|
|
295
|
+
} else {
|
|
296
|
+
const result = await spmCmds.installPlugin(source.trim());
|
|
297
|
+
if (result.message) process.stdout.write(result.message);
|
|
298
|
+
}
|
|
299
|
+
r.displayPrompt();
|
|
300
|
+
})();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
r.defineCommand('create', {
|
|
305
|
+
help: 'Create a new plugin: spm.create <name> [js|dsl|voice|ai]',
|
|
306
|
+
action: function (input) {
|
|
307
|
+
this.clearBufferedCommand();
|
|
308
|
+
(async () => {
|
|
309
|
+
const parts = input.trim().split(/\s+/);
|
|
310
|
+
const name = parts[0];
|
|
311
|
+
const template = parts[1] || 'js';
|
|
312
|
+
if (!name) {
|
|
313
|
+
console.log('Usage: spm.create <name> [js|dsl|voice|ai]');
|
|
314
|
+
} else {
|
|
315
|
+
const result = await spmCmds.createPlugin(name, template);
|
|
316
|
+
if (result.message) process.stdout.write(result.message);
|
|
317
|
+
}
|
|
318
|
+
r.displayPrompt();
|
|
319
|
+
})();
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
r.defineCommand('enable', {
|
|
324
|
+
help: 'Enable a plugin: spm.enable <name>',
|
|
325
|
+
action: function (name) {
|
|
326
|
+
this.clearBufferedCommand();
|
|
327
|
+
(async () => {
|
|
328
|
+
if (!name.trim()) {
|
|
329
|
+
console.log('Usage: spm.enable <name>');
|
|
330
|
+
} else {
|
|
331
|
+
const result = await spmCmds.enablePlugin(name.trim());
|
|
332
|
+
if (result.message) process.stdout.write(result.message);
|
|
333
|
+
}
|
|
334
|
+
r.displayPrompt();
|
|
335
|
+
})();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
r.defineCommand('disable', {
|
|
340
|
+
help: 'Disable a plugin: spm.disable <name>',
|
|
341
|
+
action: function (name) {
|
|
342
|
+
this.clearBufferedCommand();
|
|
343
|
+
(async () => {
|
|
344
|
+
if (!name.trim()) {
|
|
345
|
+
console.log('Usage: spm.disable <name>');
|
|
346
|
+
} else {
|
|
347
|
+
const result = await spmCmds.disablePlugin(name.trim());
|
|
348
|
+
if (result.message) process.stdout.write(result.message);
|
|
349
|
+
}
|
|
350
|
+
r.displayPrompt();
|
|
351
|
+
})();
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
r.defineCommand('remove', {
|
|
356
|
+
help: 'Remove a plugin: spm.remove <name>',
|
|
357
|
+
action: function (name) {
|
|
358
|
+
this.clearBufferedCommand();
|
|
359
|
+
(async () => {
|
|
360
|
+
if (!name.trim()) {
|
|
361
|
+
console.log('Usage: spm.remove <name>');
|
|
362
|
+
} else {
|
|
363
|
+
const result = await spmCmds.removePlugin(name.trim());
|
|
364
|
+
if (result.message) process.stdout.write(result.message);
|
|
365
|
+
}
|
|
366
|
+
r.displayPrompt();
|
|
367
|
+
})();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
r.defineCommand('test', {
|
|
372
|
+
help: 'Test a plugin: spm.test <name>',
|
|
373
|
+
action: function (name) {
|
|
374
|
+
this.clearBufferedCommand();
|
|
375
|
+
(async () => {
|
|
376
|
+
if (!name.trim()) {
|
|
377
|
+
console.log('Usage: spm.test <name>');
|
|
378
|
+
} else {
|
|
379
|
+
const result = await spmCmds.testPlugin(name.trim());
|
|
380
|
+
if (result.message) process.stdout.write(result.message);
|
|
381
|
+
}
|
|
382
|
+
r.displayPrompt();
|
|
383
|
+
})();
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
r.defineCommand('info', {
|
|
388
|
+
help: 'Show plugin info: spm.info <name>',
|
|
389
|
+
action: function (name) {
|
|
390
|
+
this.clearBufferedCommand();
|
|
391
|
+
(async () => {
|
|
392
|
+
if (!name.trim()) {
|
|
393
|
+
console.log('Usage: spm.info <name>');
|
|
394
|
+
} else {
|
|
395
|
+
const result = await spmCmds.infoPlugin(name.trim());
|
|
396
|
+
if (result.message) process.stdout.write(result.message);
|
|
397
|
+
}
|
|
398
|
+
r.displayPrompt();
|
|
399
|
+
})();
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
r.defineCommand('update', {
|
|
404
|
+
help: 'Update all plugins',
|
|
405
|
+
action: function () {
|
|
406
|
+
this.clearBufferedCommand();
|
|
407
|
+
(async () => {
|
|
408
|
+
const result = await spmCmds.updatePlugins();
|
|
409
|
+
if (result.message) process.stdout.write(result.message);
|
|
410
|
+
r.displayPrompt();
|
|
411
|
+
})();
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
for (const [cmdName, cmd] of Object.entries(pluginCmds)) {
|
|
416
|
+
r.defineCommand(cmdName, {
|
|
417
|
+
help: cmd.description || `Plugin command (${cmd.plugin})`,
|
|
418
|
+
action: function (args) {
|
|
419
|
+
this.clearBufferedCommand();
|
|
420
|
+
(async () => {
|
|
421
|
+
try {
|
|
422
|
+
const ctx = {
|
|
423
|
+
runtime,
|
|
424
|
+
client: runtime.client,
|
|
425
|
+
db: runtime.db,
|
|
426
|
+
manager: runtime.pluginManager,
|
|
427
|
+
config: runtime.config,
|
|
428
|
+
user: runtime.client.user
|
|
429
|
+
};
|
|
430
|
+
const result = await cmd.execute(args.split(/\s+/), ctx);
|
|
431
|
+
if (result) console.log(result);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
console.error(`[${cmdName}] Error:`, err.message);
|
|
434
|
+
}
|
|
435
|
+
r.displayPrompt();
|
|
436
|
+
})();
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
r.on('exit', () => {
|
|
442
|
+
console.log('Bot continues running. Press Ctrl+C to stop.');
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function clearSpiralCache() {
|
|
447
|
+
const spiralRoot = path.resolve(__dirname, '..');
|
|
448
|
+
Object.keys(require.cache).forEach(key => {
|
|
449
|
+
if (key.startsWith(spiralRoot + path.sep) && !key.includes('node_modules')) {
|
|
450
|
+
delete require.cache[key];
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function restart() {
|
|
456
|
+
if (restarting) {
|
|
457
|
+
pendingRestart = true;
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
restarting = true;
|
|
461
|
+
console.log('\n[dev] File changed, restarting...');
|
|
462
|
+
try {
|
|
463
|
+
if (runtime) await runtime.stop();
|
|
464
|
+
} catch {}
|
|
465
|
+
clearSpiralCache();
|
|
466
|
+
try {
|
|
467
|
+
await start();
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error('[dev] Restart failed:', error.message);
|
|
470
|
+
}
|
|
471
|
+
restarting = false;
|
|
472
|
+
if (pendingRestart) {
|
|
473
|
+
pendingRestart = false;
|
|
474
|
+
restart();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const watchDirs = [
|
|
479
|
+
path.join(process.cwd(), 'plugins'),
|
|
480
|
+
path.join(process.cwd(), 'src')
|
|
481
|
+
].filter(d => fs.existsSync(d));
|
|
482
|
+
|
|
483
|
+
const watcher = chokidar.watch(watchDirs, {
|
|
484
|
+
ignored: /node_modules|\.git|data\.json|spiral\.json/,
|
|
485
|
+
persistent: true,
|
|
486
|
+
ignoreInitial: true,
|
|
487
|
+
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
watcher.on('change', (filePath) => {
|
|
491
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.dsl') || filePath.endsWith('.json')) {
|
|
492
|
+
console.log(`[dev] Changed: ${path.relative(process.cwd(), filePath)}`);
|
|
493
|
+
restart().catch(() => {});
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
await start();
|
|
498
|
+
|
|
499
|
+
process.on('SIGINT', async () => {
|
|
500
|
+
console.log('\nShutting down...');
|
|
501
|
+
watcher.close();
|
|
502
|
+
if (runtime) await runtime.stop();
|
|
503
|
+
process.exit(0);
|
|
504
|
+
});
|
|
505
|
+
} else {
|
|
506
|
+
const runtime = await startBot(config);
|
|
507
|
+
console.log('Bot is running! Press Ctrl+C to stop.');
|
|
508
|
+
|
|
509
|
+
process.on('SIGINT', async () => {
|
|
510
|
+
console.log('\nShutting down...');
|
|
511
|
+
await runtime.stop();
|
|
512
|
+
process.exit(0);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
169
515
|
} catch (error) {
|
|
170
516
|
console.error('Failed to start bot:', error.message);
|
|
171
517
|
process.exit(1);
|
|
@@ -176,7 +522,12 @@ program
|
|
|
176
522
|
.command('dev')
|
|
177
523
|
.description('Start bot with auto-reload on file changes')
|
|
178
524
|
.action(async () => {
|
|
179
|
-
const chokidar = (() => {
|
|
525
|
+
const chokidar = (() => {
|
|
526
|
+
try {
|
|
527
|
+
const resolved = require.resolve('chokidar', { paths: [process.cwd()] });
|
|
528
|
+
return require(resolved);
|
|
529
|
+
} catch { return null; }
|
|
530
|
+
})();
|
|
180
531
|
if (!chokidar) {
|
|
181
532
|
console.error('Error: "spiral dev" requires chokidar. Run: npm install chokidar');
|
|
182
533
|
process.exit(1);
|
package/package.json
CHANGED
package/src/plugins/manager.js
CHANGED
|
@@ -255,6 +255,21 @@ class SpiralPluginManager extends EventEmitter {
|
|
|
255
255
|
return result;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
getReplCommands() {
|
|
259
|
+
const result = {};
|
|
260
|
+
for (const [pluginName, { module: mod }] of this.plugins) {
|
|
261
|
+
if (mod.repl) {
|
|
262
|
+
for (const [cmdName, cmd] of Object.entries(mod.repl)) {
|
|
263
|
+
result[cmdName] = {
|
|
264
|
+
...cmd,
|
|
265
|
+
plugin: pluginName
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
258
273
|
async executeCommand(name, message, args) {
|
|
259
274
|
const cmd = this.getCommand(name);
|
|
260
275
|
if (!cmd) return false;
|
package/src/types/index.d.ts
CHANGED
|
@@ -44,11 +44,26 @@ export interface SpiralHooks {
|
|
|
44
44
|
[key: string]: ((payload: any, runtime: SpiralRuntime) => Promise<void>) | undefined;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export interface ReplContext {
|
|
48
|
+
runtime: SpiralRuntime;
|
|
49
|
+
client: any;
|
|
50
|
+
db: SpiralDB;
|
|
51
|
+
manager: SpiralPluginManager;
|
|
52
|
+
config: SpiralConfig;
|
|
53
|
+
user: any;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PluginReplCommand {
|
|
57
|
+
description: string;
|
|
58
|
+
execute: (args: string[], context: ReplContext) => Promise<string | void> | string | void;
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
export interface SpiralPlugin {
|
|
48
62
|
init?: (config: SpiralPluginConfig, runtime: SpiralRuntime) => Promise<void>;
|
|
49
63
|
commands?: Record<string, SpiralCommand>;
|
|
50
64
|
keywords?: Record<string, (message: any, args: string[], runtime: SpiralRuntime) => Promise<void>>;
|
|
51
65
|
hooks?: SpiralHooks;
|
|
66
|
+
repl?: Record<string, PluginReplCommand>;
|
|
52
67
|
api?: Record<string, (...args: any[]) => any>;
|
|
53
68
|
}
|
|
54
69
|
|
|
@@ -66,6 +81,7 @@ export interface SpiralRuntime {
|
|
|
66
81
|
config: SpiralConfig;
|
|
67
82
|
client: any;
|
|
68
83
|
db: SpiralDB;
|
|
84
|
+
pluginManager: SpiralPluginManager;
|
|
69
85
|
startTime: number;
|
|
70
86
|
start(): Promise<void>;
|
|
71
87
|
restart(): Promise<void>;
|
|
@@ -80,6 +96,16 @@ export interface SpiralRuntime {
|
|
|
80
96
|
emitHookWithResult(eventName: string, payload: any): Promise<boolean>;
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
export interface SpiralPluginManager {
|
|
100
|
+
loadPlugins(pluginsDir: string): Promise<void>;
|
|
101
|
+
reloadPlugins(): Promise<void>;
|
|
102
|
+
getCommand(name: string): any;
|
|
103
|
+
getAllCommands(): Map<string, any>;
|
|
104
|
+
getReplCommands(): Record<string, PluginReplCommand & { plugin: string }>;
|
|
105
|
+
getSlashCommands(): Array<{ name: string; description: string }>;
|
|
106
|
+
executeCommand(name: string, message: any, args: string[]): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
|
|
83
109
|
export interface SpiralTester {
|
|
84
110
|
test(): Promise<{
|
|
85
111
|
passed: number;
|