spiralcord-full 2.1.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 ADDED
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const { execSync } = require('child_process');
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('spiral')
12
+ .description('Spiralcord - Discord Bot Runtime & Maker')
13
+ .version('2.0.0');
14
+
15
+ program
16
+ .command('install <repo>')
17
+ .description('Clone a Spiralcord bot to current directory')
18
+ .option('--no-install', 'Skip npm install')
19
+ .action(async (repo, options) => {
20
+ const cwd = process.cwd();
21
+
22
+ if (fs.existsSync(path.join(cwd, 'spiral.json'))) {
23
+ console.error('Error: spiral.json already exists here. Use "spiral update" to update.');
24
+ return;
25
+ }
26
+
27
+ if (fs.existsSync(path.join(cwd, '.git'))) {
28
+ console.error('Error: Already a git repo. Use "spiral update" to update.');
29
+ return;
30
+ }
31
+
32
+ let repoUrl = repo;
33
+ if (!repo.includes('github.com') && !repo.startsWith('http')) {
34
+ repoUrl = `https://github.com/${repo}`;
35
+ }
36
+
37
+ console.log(`Installing from ${repoUrl}...`);
38
+
39
+ try {
40
+ execSync('git init', { cwd, stdio: 'inherit' });
41
+ execSync(`git remote add origin ${repoUrl}`, { cwd, stdio: 'inherit' });
42
+ execSync('git fetch origin main --depth 1', { cwd, stdio: 'inherit' });
43
+ execSync('git checkout -b main FETCH_HEAD', { cwd, stdio: 'inherit' });
44
+ } catch (error) {
45
+ console.error('Failed to clone repository.');
46
+ try { execSync('git clean -fd', { cwd, stdio: 'inherit' }); } catch {}
47
+ try { fs.rmSync(path.join(cwd, '.git'), { recursive: true }); } catch {}
48
+ return;
49
+ }
50
+
51
+ if (options.install !== false) {
52
+ console.log('\nInstalling dependencies...');
53
+ try {
54
+ execSync('npm install', { cwd, stdio: 'inherit' });
55
+ } catch (error) {
56
+ console.error('Failed to install dependencies. Run "npm install" manually.');
57
+ }
58
+ }
59
+
60
+ console.log('\nBot installed!');
61
+ console.log('\nNext steps:');
62
+ console.log(' Edit spiral.json to add your bot token');
63
+ console.log(' Run: spiral run');
64
+ });
65
+
66
+ program
67
+ .command('update')
68
+ .description('Update bot from GitHub (preserves token and configs)')
69
+ .option('--no-install', 'Skip npm install')
70
+ .action(async (options) => {
71
+ const cwd = process.cwd();
72
+
73
+ if (!fs.existsSync(path.join(cwd, 'spiral.json'))) {
74
+ console.error('Error: spiral.json not found. Run "spiral install" first.');
75
+ return;
76
+ }
77
+
78
+ if (!fs.existsSync(path.join(cwd, '.git'))) {
79
+ console.error('Error: Not a git repo. Run "spiral install" first.');
80
+ return;
81
+ }
82
+
83
+ console.log('Saving configs...');
84
+
85
+ const spiralConfig = fs.readFileSync(path.join(cwd, 'spiral.json'), 'utf-8');
86
+
87
+ const pluginConfigs = {};
88
+ const pluginsDir = path.join(cwd, 'plugins');
89
+ if (fs.existsSync(pluginsDir)) {
90
+ for (const plugin of fs.readdirSync(pluginsDir)) {
91
+ const configPath = path.join(pluginsDir, plugin, 'config.json');
92
+ if (fs.existsSync(configPath)) {
93
+ pluginConfigs[plugin] = fs.readFileSync(configPath, 'utf-8');
94
+ }
95
+ }
96
+ }
97
+
98
+ console.log('Pulling updates...');
99
+
100
+ try {
101
+ execSync('git fetch origin main', { cwd, stdio: 'inherit' });
102
+ execSync('git reset --hard origin/main', { cwd, stdio: 'inherit' });
103
+ } catch (error) {
104
+ console.error('Failed to pull updates.');
105
+ return;
106
+ }
107
+
108
+ console.log('Restoring configs...');
109
+
110
+ fs.writeFileSync(path.join(cwd, 'spiral.json'), spiralConfig);
111
+
112
+ if (fs.existsSync(pluginsDir)) {
113
+ for (const [plugin, config] of Object.entries(pluginConfigs)) {
114
+ const configPath = path.join(pluginsDir, plugin, 'config.json');
115
+ if (fs.existsSync(path.join(pluginsDir, plugin))) {
116
+ fs.writeFileSync(configPath, config);
117
+ }
118
+ }
119
+ }
120
+
121
+ if (options.install !== false) {
122
+ console.log('\nInstalling dependencies...');
123
+ try {
124
+ execSync('npm install', { cwd, stdio: 'inherit' });
125
+ } catch (error) {
126
+ console.error('Failed to install dependencies. Run "npm install" manually.');
127
+ }
128
+ }
129
+
130
+ console.log('\nBot updated!');
131
+ console.log('Run: spiral run');
132
+ });
133
+
134
+ program
135
+ .command('run')
136
+ .description('Start the bot runtime')
137
+ .action(async () => {
138
+ const configPath = path.join(process.cwd(), 'spiral.json');
139
+
140
+ if (!fs.existsSync(configPath)) {
141
+ console.error('Error: spiral.json not found. Use "spiral install" to clone a bot.');
142
+ process.exit(1);
143
+ }
144
+
145
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
146
+
147
+ if (!config.token || config.token === 'YOUR_DISCORD_TOKEN_HERE') {
148
+ console.error('Error: Please set your bot token in spiral.json');
149
+ process.exit(1);
150
+ }
151
+
152
+ const SpiralRuntime = require('../src/runtime');
153
+ const runtime = new SpiralRuntime(config);
154
+
155
+ try {
156
+ await runtime.start();
157
+ console.log('Bot is running! Press Ctrl+C to stop.');
158
+
159
+ process.on('SIGINT', async () => {
160
+ console.log('\nShutting down...');
161
+ await runtime.stop();
162
+ process.exit(0);
163
+ });
164
+ } catch (error) {
165
+ console.error('Failed to start bot:', error.message);
166
+ process.exit(1);
167
+ }
168
+ });
169
+
170
+ program
171
+ .command('web')
172
+ .description('Start the web plugin manager')
173
+ .option('-p, --port <port>', 'Port number', '3000')
174
+ .action(async (options) => {
175
+ const SpiralWeb = require('../src/web/server');
176
+ const web = new SpiralWeb(null);
177
+
178
+ process.env.PORT = options.port;
179
+ web.port = parseInt(options.port);
180
+
181
+ web.start();
182
+ console.log(`\nSpiralcord Web Manager started!`);
183
+ console.log(`Open http://localhost:${options.port} in your browser`);
184
+ console.log(`Press Ctrl+C to stop`);
185
+ });
186
+
187
+ program
188
+ .command('test')
189
+ .description('Test all plugins for errors and conflicts')
190
+ .action(async () => {
191
+ const pluginsDir = path.join(process.cwd(), 'plugins');
192
+
193
+ if (!fs.existsSync(pluginsDir)) {
194
+ console.error('Error: No plugins directory found. Run "spiral install" first.');
195
+ process.exit(1);
196
+ }
197
+
198
+ console.log('Testing plugins...\n');
199
+
200
+ const SpiralTester = require('../src/test');
201
+ const tester = new SpiralTester(pluginsDir);
202
+ const result = await tester.test();
203
+
204
+ for (const r of result.results) {
205
+ const icon = r.status === 'ok' ? '\x1b[32m✓\x1b[0m' :
206
+ r.status === 'warning' ? '\x1b[33m⚠\x1b[0m' :
207
+ '\x1b[31m✗\x1b[0m';
208
+
209
+ const extra = r.commands > 0 ? ` (${r.commands} commands)` : '';
210
+ console.log(`${icon} ${r.name}${extra}`);
211
+
212
+ for (const issue of r.issues) {
213
+ console.log(` → ${issue}`);
214
+ }
215
+ }
216
+
217
+ console.log('\n' + '─'.repeat(40));
218
+
219
+ const passedColor = result.errors === 0 ? '\x1b[32m' : '\x1b[31m';
220
+ console.log(`${passedColor}Results: ${result.passed} passed, ${result.warnings} warnings, ${result.errors} errors\x1b[0m`);
221
+
222
+ if (result.details.errors.length > 0) {
223
+ console.log('\n\x1b[31mErrors:\x1b[0m');
224
+ for (const err of result.details.errors) {
225
+ console.log(` ✗ ${err}`);
226
+ }
227
+ }
228
+
229
+ if (result.details.warnings.length > 0) {
230
+ console.log('\n\x1b[33mWarnings:\x1b[0m');
231
+ for (const warn of result.details.warnings) {
232
+ console.log(` ⚠ ${warn}`);
233
+ }
234
+ }
235
+
236
+ if (result.errors > 0) {
237
+ console.log('\n\x1b[31mFix errors before running the bot.\x1b[0m');
238
+ process.exit(1);
239
+ } else {
240
+ console.log('\n\x1b[32mAll tests passed! Run "spiral run" to start.\x1b[0m');
241
+ }
242
+ });
243
+
244
+ const pluginCmd = program
245
+ .command('plugin')
246
+ .description('Manage plugins');
247
+
248
+ pluginCmd
249
+ .command('list')
250
+ .description('List installed plugins')
251
+ .action(() => {
252
+ const pluginsDir = path.join(process.cwd(), 'plugins');
253
+
254
+ if (!fs.existsSync(pluginsDir)) {
255
+ console.log('No plugins directory found. Use "spiral install" to clone a bot.');
256
+ return;
257
+ }
258
+
259
+ const enabled = [];
260
+ const disabled = [];
261
+
262
+ const folders = fs.readdirSync(pluginsDir);
263
+ for (const f of folders) {
264
+ const isDisabled = f.startsWith('.') && (f.endsWith('.disabled') || !fs.existsSync(path.join(pluginsDir, f, 'plugin.json')));
265
+ const pluginDirName = f.endsWith('.disabled') ? f.slice(1, -9) : f;
266
+ const pluginPath = path.join(pluginsDir, pluginDirName, 'plugin.json');
267
+ const manifestPath = path.join(pluginsDir, f, 'plugin.json');
268
+
269
+ const actualManifestPath = fs.existsSync(manifestPath) ? manifestPath : pluginPath;
270
+ if (fs.existsSync(actualManifestPath)) {
271
+ const manifest = JSON.parse(fs.readFileSync(actualManifestPath, 'utf-8'));
272
+ if (isDisabled) {
273
+ disabled.push(manifest);
274
+ } else {
275
+ enabled.push(manifest);
276
+ }
277
+ }
278
+ }
279
+
280
+ if (enabled.length === 0 && disabled.length === 0) {
281
+ console.log('No plugins installed.');
282
+ return;
283
+ }
284
+
285
+ console.log('Installed plugins:');
286
+ for (const m of enabled) {
287
+ console.log(` [enabled] ${m.name} v${m.version}: ${m.description}`);
288
+ }
289
+ for (const m of disabled) {
290
+ console.log(` [disabled] ${m.name} v${m.version}: ${m.description}`);
291
+ }
292
+ });
293
+
294
+ pluginCmd
295
+ .command('install <source>')
296
+ .description('Install a plugin from GitHub or npm')
297
+ .action(async (source) => {
298
+ const cwd = process.cwd();
299
+ const pluginsDir = path.join(cwd, 'plugins');
300
+
301
+ if (!fs.existsSync(pluginsDir)) {
302
+ fs.mkdirSync(pluginsDir, { recursive: true });
303
+ }
304
+
305
+ let repoUrl = source;
306
+ let pluginName = '';
307
+
308
+ if (source.includes('github.com')) {
309
+ repoUrl = source;
310
+ const match = source.match(/github\.com\/[^\/]+\/([^\/\.]+)/);
311
+ pluginName = match ? match[1] : '';
312
+ } else if (source.startsWith('http')) {
313
+ repoUrl = source;
314
+ const match = source.match(/\/([^\/\.]+?)(?:\.git)?$/);
315
+ pluginName = match ? match[1] : '';
316
+ } else {
317
+ pluginName = source;
318
+ repoUrl = `https://github.com/${source}`;
319
+ }
320
+
321
+ if (!pluginName) {
322
+ console.error('Could not determine plugin name from source.');
323
+ return;
324
+ }
325
+
326
+ const pluginDir = path.join(pluginsDir, pluginName);
327
+
328
+ if (fs.existsSync(pluginDir)) {
329
+ console.error(`Plugin "${pluginName}" already exists. Remove it first.`);
330
+ return;
331
+ }
332
+
333
+ console.log(`Installing plugin "${pluginName}"...`);
334
+
335
+ try {
336
+ execSync(`git clone --depth 1 ${repoUrl} "${pluginDir}"`, { stdio: 'inherit' });
337
+
338
+ const gitDir = path.join(pluginDir, '.git');
339
+ if (fs.existsSync(gitDir)) {
340
+ fs.rmSync(gitDir, { recursive: true });
341
+ }
342
+
343
+ const manifestPath = path.join(pluginDir, 'plugin.json');
344
+ if (!fs.existsSync(manifestPath)) {
345
+ console.error('Error: plugin.json not found in the cloned repository.');
346
+ fs.rmSync(pluginDir, { recursive: true });
347
+ return;
348
+ }
349
+
350
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
351
+ console.log(`Installed ${manifest.name} v${manifest.version}`);
352
+ } catch (error) {
353
+ console.error('Failed to install plugin:', error.message);
354
+ if (fs.existsSync(pluginDir)) {
355
+ fs.rmSync(pluginDir, { recursive: true });
356
+ }
357
+ }
358
+ });
359
+
360
+ pluginCmd
361
+ .command('enable <name>')
362
+ .description('Enable a plugin')
363
+ .action((name) => {
364
+ const pluginsDir = path.join(process.cwd(), 'plugins');
365
+ const pluginDir = path.join(pluginsDir, name);
366
+ const disabledDir = path.join(pluginsDir, `.${name}.disabled`);
367
+
368
+ if (fs.existsSync(pluginDir)) {
369
+ console.log(`Plugin "${name}" is already enabled.`);
370
+ return;
371
+ }
372
+
373
+ if (fs.existsSync(disabledDir)) {
374
+ fs.renameSync(disabledDir, pluginDir);
375
+ console.log(`Plugin "${name}" enabled.`);
376
+ return;
377
+ }
378
+
379
+ console.error(`Plugin "${name}" not found.`);
380
+ });
381
+
382
+ pluginCmd
383
+ .command('disable <name>')
384
+ .description('Disable a plugin by renaming its folder')
385
+ .action((name) => {
386
+ const pluginsDir = path.join(process.cwd(), 'plugins');
387
+ const pluginDir = path.join(pluginsDir, name);
388
+ const disabledDir = path.join(pluginsDir, `.${name}.disabled`);
389
+
390
+ if (!fs.existsSync(pluginDir)) {
391
+ console.error(`Plugin "${name}" not found.`);
392
+ return;
393
+ }
394
+
395
+ fs.renameSync(pluginDir, disabledDir);
396
+ console.log(`Plugin "${name}" disabled.`);
397
+ });
398
+
399
+ pluginCmd
400
+ .command('create <name>')
401
+ .description('Create a new plugin')
402
+ .action((name) => {
403
+ const cwd = process.cwd();
404
+ const pluginDir = path.join(cwd, 'plugins', name);
405
+
406
+ if (fs.existsSync(pluginDir)) {
407
+ console.error(`Plugin "${name}" already exists.`);
408
+ return;
409
+ }
410
+
411
+ fs.mkdirSync(pluginDir, { recursive: true });
412
+
413
+ const manifest = {
414
+ name: name,
415
+ version: '1.0.0',
416
+ description: `${name} plugin`,
417
+ hooks: ['message_received']
418
+ };
419
+
420
+ const config = {
421
+ enabled: true,
422
+ message: `Hello from ${name}!`
423
+ };
424
+
425
+ const schema = {
426
+ $schema: 'http://json-schema.org/draft-07/schema#',
427
+ title: `${name} Plugin Config`,
428
+ type: 'object',
429
+ properties: {
430
+ enabled: { type: 'boolean', default: true },
431
+ message: { type: 'string', default: `Hello from ${name}!` }
432
+ }
433
+ };
434
+
435
+ const code = `module.exports = {
436
+ hooks: {
437
+ message_received: async (payload, runtime) => {
438
+ const { message } = payload;
439
+ if (message.content.startsWith('!${name}')) {
440
+ const config = runtime.getPluginConfig('${name}');
441
+ await message.reply(config.message);
442
+ }
443
+ }
444
+ }
445
+ };`;
446
+
447
+ fs.writeFileSync(path.join(pluginDir, 'plugin.json'), JSON.stringify(manifest, null, 2));
448
+ fs.writeFileSync(path.join(pluginDir, 'config.json'), JSON.stringify(config, null, 2));
449
+ fs.writeFileSync(path.join(pluginDir, 'config.schema.json'), JSON.stringify(schema, null, 2));
450
+ fs.writeFileSync(path.join(pluginDir, 'index.js'), code);
451
+
452
+ console.log(`Plugin "${name}" created at plugins/${name}/`);
453
+ });
454
+
455
+ pluginCmd
456
+ .command('remove <name>')
457
+ .description('Remove a plugin')
458
+ .action((name) => {
459
+ const pluginsDir = path.join(process.cwd(), 'plugins', name);
460
+ const disabledDir = path.join(process.cwd(), 'plugins', `.${name}.disabled`);
461
+
462
+ if (fs.existsSync(pluginsDir)) {
463
+ fs.rmSync(pluginsDir, { recursive: true });
464
+ console.log(`Plugin "${name}" removed.`);
465
+ } else if (fs.existsSync(disabledDir)) {
466
+ fs.rmSync(disabledDir, { recursive: true });
467
+ console.log(`Plugin "${name}" removed.`);
468
+ } else {
469
+ console.log(`Plugin "${name}" not found.`);
470
+ }
471
+ });
472
+
473
+ pluginCmd
474
+ .command('edit <name>')
475
+ .description('Show plugin file location for editing')
476
+ .action((name) => {
477
+ const pluginsDir = path.join(process.cwd(), 'plugins', name);
478
+
479
+ if (!fs.existsSync(pluginsDir)) {
480
+ console.error(`Plugin "${name}" not found.`);
481
+ return;
482
+ }
483
+
484
+ console.log(`Plugin "${name}" files:`);
485
+ const files = fs.readdirSync(pluginsDir);
486
+ files.forEach(f => {
487
+ const fullPath = path.join(pluginsDir, f);
488
+ const stat = fs.statSync(fullPath);
489
+ if (stat.isFile()) {
490
+ console.log(` ${f}`);
491
+ }
492
+ });
493
+ console.log(`\nDirectory: ${pluginsDir}`);
494
+ });
495
+
496
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "spiralcord-full",
3
+ "version": "2.1.0",
4
+ "description": "Spiralcord Full - Discord Bot Runtime with Voice, Music, AI, Database & Image Support",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "spiral": "./bin/spiral.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/spiral.js"
11
+ },
12
+ "keywords": [
13
+ "discord",
14
+ "bot",
15
+ "runtime",
16
+ "plugin",
17
+ "spiralcord",
18
+ "discord-bot",
19
+ "bot-framework",
20
+ "voice",
21
+ "music",
22
+ "ai",
23
+ "database",
24
+ "canvas"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "commander": "^12.1.0",
30
+ "discord.js": "^14.16.3",
31
+ "express": "^4.21.0",
32
+ "@discordjs/voice": "^0.17.0",
33
+ "@discordjs/opus": "^0.9.0",
34
+ "discord-player": "^6.6.0",
35
+ "ffmpeg-static": "^5.2.0",
36
+ "@napi-rs/canvas": "^0.1.44",
37
+ "sharp": "^0.33.0",
38
+ "mongoose": "^8.0.0",
39
+ "better-sqlite3": "^11.0.0",
40
+ "quick.db": "^9.1.0",
41
+ "openai": "^4.20.0",
42
+ "axios": "^1.6.0",
43
+ "dotenv": "^16.3.0",
44
+ "chalk": "^4.1.2",
45
+ "ms": "^2.1.3",
46
+ "uuid": "^9.0.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=16.0.0"
50
+ },
51
+ "files": [
52
+ "bin/",
53
+ "src/"
54
+ ]
55
+ }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ const SpiralRuntime = require('./runtime');
2
+ const SpiralPluginManager = require('./plugins/manager');
3
+
4
+ module.exports = {
5
+ SpiralRuntime,
6
+ SpiralPluginManager
7
+ };