spiralcord-full 2.1.0 → 2.2.7

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 CHANGED
@@ -3,14 +3,15 @@
3
3
  const { Command } = require('commander');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const { execSync } = require('child_process');
6
+ const { execSync, execFileSync } = require('child_process');
7
7
 
8
8
  const program = new Command();
9
+ const SPIRAL_ROOT = path.resolve(__dirname, '..');
9
10
 
10
11
  program
11
12
  .name('spiral')
12
13
  .description('Spiralcord - Discord Bot Runtime & Maker')
13
- .version('2.0.0');
14
+ .version(require('../package.json').version);
14
15
 
15
16
  program
16
17
  .command('install <repo>')
@@ -30,20 +31,33 @@ program
30
31
  }
31
32
 
32
33
  let repoUrl = repo;
33
- if (!repo.includes('github.com') && !repo.startsWith('http')) {
34
+ if (!repo.includes('github.com') && !repo.startsWith('http') && !repo.startsWith('git@')) {
34
35
  repoUrl = `https://github.com/${repo}`;
35
36
  }
37
+ if (!repoUrl.endsWith('.git')) repoUrl += '.git';
36
38
 
37
39
  console.log(`Installing from ${repoUrl}...`);
38
40
 
39
41
  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' });
42
+ execFileSync('git', ['init'], { cwd, stdio: 'inherit' });
43
+ execFileSync('git', ['remote', 'add', 'origin', repoUrl], { cwd, stdio: 'inherit' });
44
+
45
+ let fetched = false;
46
+ for (const branch of ['main', 'master']) {
47
+ try {
48
+ execFileSync('git', ['fetch', 'origin', branch, '--depth', '1'], { cwd, stdio: 'inherit' });
49
+ execFileSync('git', ['checkout', '-b', branch, 'FETCH_HEAD'], { cwd, stdio: 'inherit' });
50
+ fetched = true;
51
+ break;
52
+ } catch {}
53
+ }
54
+
55
+ if (!fetched) {
56
+ throw new Error('Could not fetch main or master branch');
57
+ }
44
58
  } catch (error) {
45
- console.error('Failed to clone repository.');
46
- try { execSync('git clean -fd', { cwd, stdio: 'inherit' }); } catch {}
59
+ console.error('Failed to clone repository:', error.message);
60
+ try { execFileSync('git', ['clean', '-fd'], { cwd, stdio: 'inherit' }); } catch {}
47
61
  try { fs.rmSync(path.join(cwd, '.git'), { recursive: true }); } catch {}
48
62
  return;
49
63
  }
@@ -65,95 +79,86 @@ program
65
79
 
66
80
  program
67
81
  .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');
82
+ .description('Update spiralcord to latest version')
83
+ .action(async () => {
84
+ const pkgPath = path.join(SPIRAL_ROOT, 'package.json');
85
+ const currentVer = require(pkgPath).version;
86
+ console.log(`Current version: v${currentVer}`);
87
+ console.log('Updating spiralcord...');
86
88
 
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
- }
89
+ try {
90
+ execSync('npm install -g spiralcord@latest', { stdio: 'inherit' });
91
+ delete require.cache[require.resolve(pkgPath)];
92
+ const newVer = require(pkgPath).version;
93
+ if (newVer !== currentVer) {
94
+ console.log(`\nUpdated: v${currentVer} → v${newVer}`);
95
+ } else {
96
+ console.log('\nAlready up to date!');
95
97
  }
98
+ } catch (error) {
99
+ console.error('Failed to update:', error.message);
96
100
  }
101
+ });
97
102
 
98
- console.log('Pulling updates...');
103
+ async function loadConfig() {
104
+ const configPath = path.join(process.cwd(), 'spiral.json');
105
+
106
+ if (!fs.existsSync(configPath)) {
107
+ console.log('spiral.json not found. Creating default config...');
108
+ const defaultConfig = {
109
+ name: 'My Spiral Bot',
110
+ token: '',
111
+ prefix: '!',
112
+ intents: ['Guilds', 'GuildMessages', 'MessageContent'],
113
+ clientId: '',
114
+ disabledCommands: []
115
+ };
116
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
117
+ console.log('Created spiral.json — edit it with your bot token, then run "spiral run".');
118
+ return null;
119
+ }
99
120
 
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
- }
121
+ let config;
122
+ try {
123
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
124
+ } catch (e) {
125
+ console.error(`Error: spiral.json is invalid JSON: ${e.message}`);
126
+ return null;
127
+ }
107
128
 
108
- console.log('Restoring configs...');
129
+ if (!config || typeof config !== 'object') {
130
+ console.error('Error: spiral.json must be a JSON object');
131
+ return null;
132
+ }
109
133
 
110
- fs.writeFileSync(path.join(cwd, 'spiral.json'), spiralConfig);
134
+ if (!config.token || config.token === '') {
135
+ console.error('Error: Set your bot token in spiral.json');
136
+ return null;
137
+ }
111
138
 
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
- }
139
+ if (!Array.isArray(config.intents)) {
140
+ console.warn('Warning: Missing intents in spiral.json, using defaults');
141
+ config.intents = ['Guilds', 'GuildMessages', 'MessageContent'];
142
+ }
120
143
 
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
- }
144
+ return config;
145
+ }
129
146
 
130
- console.log('\nBot updated!');
131
- console.log('Run: spiral run');
132
- });
147
+ async function startBot(config) {
148
+ const SpiralRuntime = require('../src/runtime');
149
+ const runtime = new SpiralRuntime(config);
150
+ await runtime.start();
151
+ return runtime;
152
+ }
133
153
 
134
154
  program
135
155
  .command('run')
136
156
  .description('Start the bot runtime')
137
157
  .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
158
  try {
156
- await runtime.start();
159
+ const config = await loadConfig();
160
+ if (!config) return;
161
+ const runtime = await startBot(config);
157
162
  console.log('Bot is running! Press Ctrl+C to stop.');
158
163
 
159
164
  process.on('SIGINT', async () => {
@@ -168,329 +173,247 @@ program
168
173
  });
169
174
 
170
175
  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')
176
+ .command('dev')
177
+ .description('Start bot with auto-reload on file changes')
190
178
  .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.');
179
+ const chokidar = (() => { try { return require('chokidar'); } catch { return null; } })();
180
+ if (!chokidar) {
181
+ console.error('Error: "spiral dev" requires chokidar. Run: npm install chokidar');
195
182
  process.exit(1);
196
183
  }
197
184
 
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();
185
+ let runtime = null;
186
+ let restarting = false;
187
+ let pendingRestart = false;
203
188
 
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
- }
189
+ async function start() {
190
+ const config = await loadConfig();
191
+ if (!config) return;
192
+ runtime = await startBot(config);
193
+ console.log('Dev mode active — watching for changes. Press Ctrl+C to stop.');
215
194
  }
216
195
 
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}`);
196
+ async function restart() {
197
+ if (restarting) {
198
+ pendingRestart = true;
199
+ return;
226
200
  }
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}`);
201
+ restarting = true;
202
+ console.log('\n[dev] File changed, restarting...');
203
+ try {
204
+ if (runtime) await runtime.stop();
205
+ } catch {}
206
+ const spiralRoot = path.resolve(__dirname, '..');
207
+ Object.keys(require.cache).forEach(key => {
208
+ if (key.startsWith(spiralRoot + path.sep) && !key.includes('node_modules')) {
209
+ delete require.cache[key];
210
+ }
211
+ });
212
+ try {
213
+ await start();
214
+ } catch (error) {
215
+ console.error('[dev] Restart failed:', error.message);
216
+ }
217
+ restarting = false;
218
+ if (pendingRestart) {
219
+ pendingRestart = false;
220
+ restart();
233
221
  }
234
222
  }
235
223
 
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
- });
224
+ const watchDirs = [
225
+ path.join(process.cwd(), 'plugins'),
226
+ path.join(process.cwd(), 'src')
227
+ ].filter(d => fs.existsSync(d));
243
228
 
244
- const pluginCmd = program
245
- .command('plugin')
246
- .description('Manage plugins');
229
+ const watcher = chokidar.watch(watchDirs, {
230
+ ignored: /node_modules|\.git|data\.json|spiral\.json/,
231
+ persistent: true,
232
+ ignoreInitial: true,
233
+ awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
234
+ });
247
235
 
248
- pluginCmd
249
- .command('list')
250
- .description('List installed plugins')
251
- .action(() => {
252
- const pluginsDir = path.join(process.cwd(), 'plugins');
236
+ watcher.on('change', (filePath) => {
237
+ if (filePath.endsWith('.js') || filePath.endsWith('.dsl') || filePath.endsWith('.json')) {
238
+ console.log(`[dev] Changed: ${path.relative(process.cwd(), filePath)}`);
239
+ restart().catch(() => {});
240
+ }
241
+ });
253
242
 
254
- if (!fs.existsSync(pluginsDir)) {
255
- console.log('No plugins directory found. Use "spiral install" to clone a bot.');
256
- return;
243
+ try {
244
+ await start();
245
+ } catch (error) {
246
+ console.error('Failed to start bot:', error.message);
247
+ process.exit(1);
257
248
  }
258
249
 
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
- }
250
+ process.on('SIGINT', async () => {
251
+ console.log('\nShutting down...');
252
+ watcher.close();
253
+ if (runtime) await runtime.stop();
254
+ process.exit(0);
255
+ });
256
+ });
279
257
 
280
- if (enabled.length === 0 && disabled.length === 0) {
281
- console.log('No plugins installed.');
258
+ program
259
+ .command('web')
260
+ .description('Start the web plugin manager')
261
+ .option('-p, --port <port>', 'Port number', '3000')
262
+ .action(async (options) => {
263
+ const port = parseInt(options.port, 10);
264
+ if (isNaN(port) || port < 1 || port > 65535) {
265
+ console.error('Error: Invalid port number');
282
266
  return;
283
267
  }
284
268
 
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}`);
269
+ try {
270
+ const SpiralWeb = require('../src/web/server');
271
+ const web = new SpiralWeb(null);
272
+ process.env.PORT = String(port);
273
+ web.port = port;
274
+ web.start();
275
+ console.log(`\nSpiralcord Web Manager started!`);
276
+ console.log(`Open http://localhost:${port} in your browser`);
277
+ console.log(`Press Ctrl+C to stop`);
278
+ } catch (error) {
279
+ console.error('Failed to start web manager:', error.message);
291
280
  }
292
281
  });
293
282
 
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');
283
+ program
284
+ .command('test')
285
+ .description('Test all plugins for errors and conflicts')
286
+ .action(async () => {
287
+ const pluginsDir = path.join(process.cwd(), 'plugins');
300
288
 
301
289
  if (!fs.existsSync(pluginsDir)) {
302
- fs.mkdirSync(pluginsDir, { recursive: true });
290
+ console.error('Error: No plugins directory found. Run "spiral install" first.');
291
+ process.exit(1);
303
292
  }
304
293
 
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
- }
294
+ console.log('Testing plugins...\n');
320
295
 
321
- if (!pluginName) {
322
- console.error('Could not determine plugin name from source.');
323
- return;
324
- }
296
+ try {
297
+ const SpiralTester = require('../src/test');
298
+ const tester = new SpiralTester(pluginsDir);
299
+ const result = await tester.test();
325
300
 
326
- const pluginDir = path.join(pluginsDir, pluginName);
301
+ for (const r of result.results) {
302
+ const icon = r.status === 'ok' ? '\x1b[32m✓\x1b[0m' :
303
+ r.status === 'warning' ? '\x1b[33m⚠\x1b[0m' :
304
+ '\x1b[31m✗\x1b[0m';
327
305
 
328
- if (fs.existsSync(pluginDir)) {
329
- console.error(`Plugin "${pluginName}" already exists. Remove it first.`);
330
- return;
331
- }
306
+ const extra = r.commands > 0 ? ` (${r.commands} commands)` : '';
307
+ console.log(`${icon} ${r.name}${extra}`);
332
308
 
333
- console.log(`Installing plugin "${pluginName}"...`);
309
+ for (const issue of r.issues) {
310
+ console.log(` → ${issue}`);
311
+ }
312
+ }
334
313
 
335
- try {
336
- execSync(`git clone --depth 1 ${repoUrl} "${pluginDir}"`, { stdio: 'inherit' });
314
+ console.log('\n' + '─'.repeat(40));
337
315
 
338
- const gitDir = path.join(pluginDir, '.git');
339
- if (fs.existsSync(gitDir)) {
340
- fs.rmSync(gitDir, { recursive: true });
341
- }
316
+ const passedColor = result.errors === 0 ? '\x1b[32m' : '\x1b[31m';
317
+ console.log(`${passedColor}Results: ${result.passed} passed, ${result.warnings} warnings, ${result.errors} errors\x1b[0m`);
342
318
 
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;
319
+ if (result.details.errors.length > 0) {
320
+ console.log('\n\x1b[31mErrors:\x1b[0m');
321
+ for (const err of result.details.errors) {
322
+ console.log(` ✗ ${err}`);
323
+ }
348
324
  }
349
325
 
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 });
326
+ if (result.details.warnings.length > 0) {
327
+ console.log('\n\x1b[33mWarnings:\x1b[0m');
328
+ for (const warn of result.details.warnings) {
329
+ console.log(` ⚠ ${warn}`);
330
+ }
356
331
  }
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
332
 
373
- if (fs.existsSync(disabledDir)) {
374
- fs.renameSync(disabledDir, pluginDir);
375
- console.log(`Plugin "${name}" enabled.`);
376
- return;
333
+ if (result.errors > 0) {
334
+ console.log('\n\x1b[31mFix errors before running the bot.\x1b[0m');
335
+ process.exit(1);
336
+ } else {
337
+ console.log('\n\x1b[32mAll tests passed! Run "spiral run" to start.\x1b[0m');
338
+ }
339
+ } catch (error) {
340
+ console.error('Test failed:', error.message);
341
+ process.exit(1);
377
342
  }
378
-
379
- console.error(`Plugin "${name}" not found.`);
380
343
  });
381
344
 
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;
345
+ program
346
+ .command('plugin')
347
+ .description('Manage plugins (delegates to spm)')
348
+ .argument('[args...]', 'spm arguments')
349
+ .action((args) => {
350
+ const spmPath = path.join(__dirname, '..', 'spm', 'bin', 'spm.js');
351
+ try {
352
+ execFileSync('node', [spmPath, ...args], { stdio: 'inherit' });
353
+ } catch (error) {
354
+ if (error.status !== 0) {
355
+ console.error('spm exited with error');
356
+ }
393
357
  }
394
-
395
- fs.renameSync(pluginDir, disabledDir);
396
- console.log(`Plugin "${name}" disabled.`);
397
358
  });
398
359
 
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);
360
+ program
361
+ .command('doctor')
362
+ .description('Check bot health and configuration')
363
+ .action(async () => {
364
+ console.log('Spiralcord Doctor\n');
365
+ let issues = 0;
405
366
 
406
- if (fs.existsSync(pluginDir)) {
407
- console.error(`Plugin "${name}" already exists.`);
408
- return;
367
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
368
+ if (nodeMajor >= 16) {
369
+ console.log(`\x1b[32m✓\x1b[0m Node.js ${process.version}`);
370
+ } else {
371
+ console.log(`\x1b[31m✗\x1b[0m Node.js ${process.version} (need >=16)`);
372
+ issues++;
409
373
  }
410
374
 
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);
375
+ const configPath = path.join(process.cwd(), 'spiral.json');
376
+ if (fs.existsSync(configPath)) {
377
+ console.log(`\x1b[32m✓\x1b[0m spiral.json found`);
378
+ try {
379
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
380
+ if (config.token && config.token !== '' && config.token !== 'YOUR_DISCORD_TOKEN_HERE') {
381
+ console.log(`\x1b[32m✓\x1b[0m Bot token configured`);
382
+ } else {
383
+ console.log(`\x1b[31m✗\x1b[0m Bot token not set`);
384
+ issues++;
385
+ }
386
+ if (config.intents && config.intents.length > 0) {
387
+ console.log(`\x1b[32m✓\x1b[0m Intents: ${config.intents.join(', ')}`);
388
+ } else {
389
+ console.log(`\x1b[33m⚠\x1b[0m No intents configured`);
390
+ }
391
+ } catch (e) {
392
+ console.log(`\x1b[31m✗\x1b[0m spiral.json invalid: ${e.message}`);
393
+ issues++;
442
394
  }
395
+ } else {
396
+ console.log(`\x1b[31m✗\x1b[0m spiral.json not found`);
397
+ issues++;
443
398
  }
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
399
 
400
+ const pluginsDir = path.join(process.cwd(), 'plugins');
462
401
  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.`);
402
+ const folders = fs.readdirSync(pluginsDir).filter(f => !f.startsWith('.'));
403
+ console.log(`\x1b[32m✓\x1b[0m ${folders.length} plugin(s) found`);
468
404
  } else {
469
- console.log(`Plugin "${name}" not found.`);
405
+ console.log(`\x1b[33m⚠\x1b[0m No plugins directory`);
470
406
  }
471
- });
472
407
 
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);
408
+ const spiralVer = require(path.join(__dirname, '..', 'package.json')).version;
409
+ console.log(`\x1b[32m✓\x1b[0m Spiralcord v${spiralVer}`);
478
410
 
479
- if (!fs.existsSync(pluginsDir)) {
480
- console.error(`Plugin "${name}" not found.`);
481
- return;
411
+ console.log('\n' + '─'.repeat(40));
412
+ if (issues === 0) {
413
+ console.log('\x1b[32mAll checks passed!\x1b[0m');
414
+ } else {
415
+ console.log(`\x1b[31m${issues} issue(s) found. Fix them before running the bot.\x1b[0m`);
482
416
  }
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
417
  });
495
418
 
496
- program.parse();
419
+ program.parse();