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 +273 -350
- package/package.json +4 -2
- package/src/plugins/manager.js +162 -34
- package/src/runtime.js +157 -22
- package/src/test.js +85 -63
- package/src/types/index.d.ts +100 -0
- package/src/web/server.js +69 -41
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('
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 {
|
|
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
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
129
|
+
if (!config || typeof config !== 'object') {
|
|
130
|
+
console.error('Error: spiral.json must be a JSON object');
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
109
133
|
|
|
110
|
-
|
|
134
|
+
if (!config.token || config.token === '') {
|
|
135
|
+
console.error('Error: Set your bot token in spiral.json');
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
111
138
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
|
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('
|
|
172
|
-
.description('Start
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
process.
|
|
239
|
-
|
|
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
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
295
|
-
.command('
|
|
296
|
-
.description('
|
|
297
|
-
.action(async (
|
|
298
|
-
const
|
|
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
|
-
|
|
290
|
+
console.error('Error: No plugins directory found. Run "spiral install" first.');
|
|
291
|
+
process.exit(1);
|
|
303
292
|
}
|
|
304
293
|
|
|
305
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
296
|
+
try {
|
|
297
|
+
const SpiralTester = require('../src/test');
|
|
298
|
+
const tester = new SpiralTester(pluginsDir);
|
|
299
|
+
const result = await tester.test();
|
|
325
300
|
|
|
326
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
306
|
+
const extra = r.commands > 0 ? ` (${r.commands} commands)` : '';
|
|
307
|
+
console.log(`${icon} ${r.name}${extra}`);
|
|
332
308
|
|
|
333
|
-
|
|
309
|
+
for (const issue of r.issues) {
|
|
310
|
+
console.log(` → ${issue}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
334
313
|
|
|
335
|
-
|
|
336
|
-
execSync(`git clone --depth 1 ${repoUrl} "${pluginDir}"`, { stdio: 'inherit' });
|
|
314
|
+
console.log('\n' + '─'.repeat(40));
|
|
337
315
|
|
|
338
|
-
const
|
|
339
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
383
|
-
.command('
|
|
384
|
-
.description('
|
|
385
|
-
.
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
400
|
-
.command('
|
|
401
|
-
.description('
|
|
402
|
-
.action((
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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.
|
|
464
|
-
console.log(
|
|
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(
|
|
405
|
+
console.log(`\x1b[33m⚠\x1b[0m No plugins directory`);
|
|
470
406
|
}
|
|
471
|
-
});
|
|
472
407
|
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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();
|