novaapp-sdk 1.4.0 → 1.4.2

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.
@@ -0,0 +1,716 @@
1
+ import {
2
+ HotLoader
3
+ } from "../chunk-LMORGVJR.mjs";
4
+ import "../chunk-53M2BTB5.mjs";
5
+
6
+ // src/cli/utils.ts
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ var isTTY = Boolean(process.stdout.isTTY);
10
+ var c = {
11
+ reset: (s) => isTTY ? `\x1B[0m${s}\x1B[0m` : s,
12
+ bold: (s) => isTTY ? `\x1B[1m${s}\x1B[0m` : s,
13
+ dim: (s) => isTTY ? `\x1B[2m${s}\x1B[0m` : s,
14
+ green: (s) => isTTY ? `\x1B[32m${s}\x1B[0m` : s,
15
+ yellow: (s) => isTTY ? `\x1B[33m${s}\x1B[0m` : s,
16
+ cyan: (s) => isTTY ? `\x1B[36m${s}\x1B[0m` : s,
17
+ red: (s) => isTTY ? `\x1B[31m${s}\x1B[0m` : s
18
+ };
19
+ var colors = c;
20
+ function print(msg) {
21
+ process.stdout.write(msg + "\n");
22
+ }
23
+ function printInfo(msg) {
24
+ print(c.cyan("\u2139") + " " + msg);
25
+ }
26
+ function printSuccess(msg) {
27
+ print(c.green("\u2714") + " " + msg);
28
+ }
29
+ function printWarning(msg) {
30
+ print(c.yellow("\u26A0") + " " + msg);
31
+ }
32
+ function printError(msg) {
33
+ print(c.red("\u2716") + " " + msg);
34
+ }
35
+ function printHeader(title) {
36
+ print("\n" + c.bold(title));
37
+ }
38
+ function printStep(step) {
39
+ print(c.dim(" \u2192") + " " + step);
40
+ }
41
+ function printTable(rows, pad = 20) {
42
+ for (const [left, right] of rows) {
43
+ print(" " + c.cyan(left.padEnd(pad)) + right);
44
+ }
45
+ }
46
+ function fatal(msg) {
47
+ printError(msg);
48
+ process.exit(1);
49
+ }
50
+ var KNOWN_COMMANDS = /* @__PURE__ */ new Set(["new", "generate", "deploy", "dev", "help"]);
51
+ function parseArgs(argv) {
52
+ const flags = {};
53
+ const positional = [];
54
+ let command = null;
55
+ let i = 0;
56
+ while (i < argv.length) {
57
+ const arg = argv[i];
58
+ if (arg.startsWith("--")) {
59
+ const [key, ...valueParts] = arg.slice(2).split("=");
60
+ if (valueParts.length) {
61
+ flags[key] = valueParts.join("=");
62
+ } else if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
63
+ flags[key] = argv[++i];
64
+ } else {
65
+ flags[key] = true;
66
+ }
67
+ } else if (arg.startsWith("-") && arg.length === 2) {
68
+ flags[arg[1]] = true;
69
+ } else {
70
+ if (command === null && KNOWN_COMMANDS.has(arg)) {
71
+ command = arg;
72
+ } else {
73
+ positional.push(arg);
74
+ }
75
+ }
76
+ i++;
77
+ }
78
+ return { command, positional, flags };
79
+ }
80
+ function mkdirp(dir) {
81
+ fs.mkdirSync(dir, { recursive: true });
82
+ }
83
+ function writeFile(filePath, content, force = false) {
84
+ const dir = path.dirname(filePath);
85
+ mkdirp(dir);
86
+ if (!force && fs.existsSync(filePath)) {
87
+ printWarning(`Skipped (already exists): ${filePath}`);
88
+ return;
89
+ }
90
+ fs.writeFileSync(filePath, content, "utf8");
91
+ printSuccess(`Created: ${filePath}`);
92
+ }
93
+ function slugify(str) {
94
+ return str.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
95
+ }
96
+ function pascalCase(str) {
97
+ return str.replace(/[-_](.)/g, (_, c2) => c2.toUpperCase()).replace(/^(.)/, (_, c2) => c2.toUpperCase());
98
+ }
99
+ function camelCase(str) {
100
+ const p = pascalCase(str);
101
+ return p.charAt(0).toLowerCase() + p.slice(1);
102
+ }
103
+
104
+ // src/cli/commands/new.ts
105
+ import * as path2 from "path";
106
+ import * as cp from "child_process";
107
+ async function runNew(args, flags) {
108
+ const name = args[0];
109
+ if (!name) fatal("Usage: nova new <project-name>");
110
+ const options = {
111
+ name,
112
+ skipInstall: flags["skip-install"] === true || flags["y"] === true
113
+ };
114
+ scaffold(options);
115
+ if (!options.skipInstall) {
116
+ printHeader("Installing dependencies\u2026");
117
+ printStep("npm install");
118
+ try {
119
+ cp.execSync("npm install", {
120
+ cwd: path2.resolve(slugify(name)),
121
+ stdio: "inherit"
122
+ });
123
+ } catch {
124
+ printWarning("npm install failed \u2014 run it manually inside the project folder.");
125
+ }
126
+ }
127
+ printHeader("Done!");
128
+ print("");
129
+ printInfo(` cd ${slugify(name)}`);
130
+ printInfo(" cp .env.example .env # add your BOT_TOKEN");
131
+ printInfo(" npm run dev");
132
+ print("");
133
+ }
134
+ function scaffold(options) {
135
+ const { name } = options;
136
+ const slug = slugify(name);
137
+ const root = path2.resolve(slug);
138
+ printHeader(`Creating Nova project "${slug}"\u2026`);
139
+ writeFile(
140
+ path2.join(root, "package.json"),
141
+ JSON.stringify(
142
+ {
143
+ name: slug,
144
+ version: "1.0.0",
145
+ type: "module",
146
+ scripts: {
147
+ build: "tsc",
148
+ start: "node dist/index.js",
149
+ dev: "nova dev"
150
+ },
151
+ dependencies: {
152
+ "novaapp-sdk": "latest"
153
+ },
154
+ devDependencies: {
155
+ typescript: "^5.0.0"
156
+ }
157
+ },
158
+ null,
159
+ 2
160
+ ) + "\n"
161
+ );
162
+ writeFile(
163
+ path2.join(root, "tsconfig.json"),
164
+ JSON.stringify(
165
+ {
166
+ compilerOptions: {
167
+ target: "ES2022",
168
+ module: "NodeNext",
169
+ moduleResolution: "NodeNext",
170
+ strict: true,
171
+ outDir: "./dist",
172
+ rootDir: "./src",
173
+ declaration: true,
174
+ skipLibCheck: true
175
+ },
176
+ include: ["src"]
177
+ },
178
+ null,
179
+ 2
180
+ ) + "\n"
181
+ );
182
+ writeFile(
183
+ path2.join(root, ".gitignore"),
184
+ [
185
+ "node_modules/",
186
+ "dist/",
187
+ ".env",
188
+ "*.log"
189
+ ].join("\n") + "\n"
190
+ );
191
+ writeFile(
192
+ path2.join(root, ".env.example"),
193
+ [
194
+ "# Copy this file to .env and fill in your values",
195
+ "BOT_TOKEN=your_token_here",
196
+ "SERVER_URL=https://nova.example.com"
197
+ ].join("\n") + "\n"
198
+ );
199
+ writeFile(
200
+ path2.join(root, "src", "index.ts"),
201
+ indexTemplate(slug)
202
+ );
203
+ writeFile(
204
+ path2.join(root, "src", "commands", "ping.ts"),
205
+ pingCommandTemplate()
206
+ );
207
+ writeFile(
208
+ path2.join(root, "src", "events", "ready.ts"),
209
+ readyEventTemplate()
210
+ );
211
+ printSuccess(`Project "${slug}" created at ./${slug}`);
212
+ }
213
+ function indexTemplate(botName) {
214
+ return `import { NovaClient } from 'novaapp-sdk'
215
+ import { ping } from './commands/ping.js'
216
+ import { onReady } from './events/ready.js'
217
+
218
+ const client = new NovaClient({
219
+ token: process.env.BOT_TOKEN ?? '',
220
+ serverUrl: process.env.SERVER_URL ?? 'https://nova.example.com',
221
+ })
222
+
223
+ // Register commands
224
+ client.commands.register(ping)
225
+
226
+ // Register events
227
+ client.on('ready', onReady)
228
+
229
+ client.connect()
230
+ `;
231
+ }
232
+ function pingCommandTemplate() {
233
+ return `import type { NovaClient, Interaction } from 'novaapp-sdk'
234
+
235
+ export const ping = {
236
+ name: 'ping',
237
+ description: 'Replies with Pong!',
238
+
239
+ async execute(client: NovaClient, interaction: Interaction) {
240
+ await client.interactions.respond(interaction.id, {
241
+ content: 'Pong! \u{1F3D3}',
242
+ })
243
+ },
244
+ }
245
+ `;
246
+ }
247
+ function readyEventTemplate() {
248
+ return `import type { BotApplication } from 'novaapp-sdk'
249
+
250
+ export function onReady(bot: BotApplication) {
251
+ console.log(\`\u2714 Logged in as \${bot.botUser?.username ?? 'Unknown'}\`)
252
+ }
253
+ `;
254
+ }
255
+
256
+ // src/cli/commands/generate.ts
257
+ import * as path3 from "path";
258
+ async function runGenerate(args, flags) {
259
+ const [kindRaw, name] = args;
260
+ if (!kindRaw) {
261
+ fatal(
262
+ "Usage: nova generate <command|event|plugin|middleware> <name>\n nova generate command ban\n nova generate event messageDelete\n nova generate plugin moderation\n nova generate middleware rateLimit"
263
+ );
264
+ }
265
+ const kind = kindRaw.toLowerCase();
266
+ const KINDS = ["command", "event", "plugin", "middleware"];
267
+ if (!KINDS.includes(kind)) {
268
+ fatal(`Unknown kind "${kindRaw}". Must be one of: ${KINDS.join(", ")}`);
269
+ }
270
+ if (!name) fatal(`Usage: nova generate ${kind} <name>`);
271
+ const options = {
272
+ kind,
273
+ name,
274
+ outDir: typeof flags["out"] === "string" ? flags["out"] : void 0
275
+ };
276
+ generate(options);
277
+ }
278
+ function generate(options) {
279
+ const { kind, name } = options;
280
+ const defaultDirs = {
281
+ command: "src/commands",
282
+ event: "src/events",
283
+ plugin: "src/plugins",
284
+ middleware: "src/middleware"
285
+ };
286
+ const outDir = options.outDir ?? defaultDirs[kind];
287
+ const fileName = camelCase(name) + ".ts";
288
+ const filePath = path3.resolve(outDir, fileName);
289
+ const generators = {
290
+ command: () => commandTemplate(name),
291
+ event: () => eventTemplate(name),
292
+ plugin: () => pluginTemplate(name),
293
+ middleware: () => middlewareTemplate(name)
294
+ };
295
+ printHeader(`Generating ${kind} "${name}"\u2026`);
296
+ writeFile(filePath, generators[kind]());
297
+ print("");
298
+ printInfo(`Register it in your bot entry:
299
+ `);
300
+ const hints = {
301
+ command: ` client.commands.register(${camelCase(name)})`,
302
+ event: ` client.on('${camelCase(name)}', handle${pascalCase(name)})`,
303
+ plugin: ` client.plugins.use(${camelCase(name)}Plugin)`,
304
+ middleware: ` client.middleware.use(${camelCase(name)}Middleware)`
305
+ };
306
+ printInfo(hints[kind]);
307
+ print("");
308
+ }
309
+ function commandTemplate(name) {
310
+ const identName = camelCase(name);
311
+ const pascal = pascalCase(name);
312
+ return `import type { NovaClient, Interaction } from 'novaapp-sdk'
313
+
314
+ /**
315
+ * /${name} command
316
+ */
317
+ export const ${identName} = {
318
+ name: '${name}',
319
+ description: 'Description of the ${name} command',
320
+
321
+ async execute(client: NovaClient, interaction: Interaction) {
322
+ await client.interactions.respond(interaction.id, {
323
+ content: '${pascal} executed!',
324
+ })
325
+ },
326
+ }
327
+ `;
328
+ }
329
+ function eventTemplate(name) {
330
+ const pascal = pascalCase(name);
331
+ return `import type { NovaClient } from 'novaapp-sdk'
332
+
333
+ /**
334
+ * Handler for the \`${name}\` event.
335
+ */
336
+ export async function handle${pascal}(
337
+ ..._args: unknown[]
338
+ ): Promise<void> {
339
+ // TODO: implement ${name} handler
340
+ }
341
+
342
+ /**
343
+ * Register this handler with a client.
344
+ */
345
+ export function register${pascal}(client: NovaClient): void {
346
+ client.on('${name}' as never, handle${pascal})
347
+ }
348
+ `;
349
+ }
350
+ function pluginTemplate(name) {
351
+ const pascal = pascalCase(name);
352
+ const ident = camelCase(name);
353
+ return `import type { NovaPlugin, NovaClient } from 'novaapp-sdk'
354
+
355
+ /**
356
+ * ${pascal} plugin.
357
+ */
358
+ interface ${pascal}PluginOptions {
359
+ // TODO: add your plugin options
360
+ }
361
+
362
+ export function ${ident}Plugin(options: ${pascal}PluginOptions = {}): NovaPlugin {
363
+ return {
364
+ name: '${ident}',
365
+
366
+ install(client: NovaClient) {
367
+ // TODO: implement plugin logic
368
+ void options // remove this line once you use options
369
+ },
370
+ }
371
+ }
372
+ `;
373
+ }
374
+ function middlewareTemplate(name) {
375
+ const pascal = pascalCase(name);
376
+ const ident = camelCase(name);
377
+ return `import type { NovaMiddleware, MiddlewareContext, MiddlewareNext } from 'novaapp-sdk'
378
+
379
+ /**
380
+ * ${pascal} middleware.
381
+ */
382
+ export const ${ident}Middleware: NovaMiddleware = async (
383
+ ctx: MiddlewareContext,
384
+ next: MiddlewareNext,
385
+ ): Promise<void> => {
386
+ // TODO: add ${name} logic here (before next())
387
+ await next()
388
+ // TODO: add ${name} logic here (after next())
389
+ }
390
+ `;
391
+ }
392
+
393
+ // src/cli/commands/deploy.ts
394
+ import * as cp2 from "child_process";
395
+ async function runDeploy(args, flags) {
396
+ const options = {
397
+ skipBuild: flags["skip-build"] === true
398
+ };
399
+ const provider = typeof flags["provider"] === "string" ? flags["provider"] : "generic";
400
+ if (!options.skipBuild) {
401
+ await build();
402
+ } else {
403
+ printWarning("Skipping build (--skip-build)");
404
+ }
405
+ printDeployInstructions(provider);
406
+ }
407
+ function build() {
408
+ return new Promise((resolve4, reject) => {
409
+ printHeader("Building project\u2026");
410
+ printStep("npm run build");
411
+ const child = cp2.spawn("npm", ["run", "build"], {
412
+ stdio: "inherit",
413
+ shell: process.platform === "win32"
414
+ });
415
+ child.on("close", (code) => {
416
+ if (code === 0) {
417
+ printSuccess("Build succeeded");
418
+ resolve4();
419
+ } else {
420
+ reject(new Error(`Build failed with exit code ${code}`));
421
+ }
422
+ });
423
+ child.on("error", reject);
424
+ });
425
+ }
426
+ function printDeployInstructions(provider) {
427
+ printHeader("Deployment guide");
428
+ print("");
429
+ if (provider === "railway" || provider === "rail") {
430
+ printRailway();
431
+ } else if (provider === "docker") {
432
+ printDocker();
433
+ } else if (provider === "vps") {
434
+ printVps();
435
+ } else {
436
+ printGeneric();
437
+ }
438
+ print("");
439
+ printInfo("Tip: pass --provider=railway|docker|vps for provider-specific instructions.");
440
+ print("");
441
+ }
442
+ function printGeneric() {
443
+ print(colors.bold(" Generic (any Node.js host)"));
444
+ print("");
445
+ printStep("Ensure BOT_TOKEN and SERVER_URL are set as environment variables");
446
+ printStep("Upload your project (dist/, package.json, .env) to the host");
447
+ printStep("Run: node dist/index.js");
448
+ print("");
449
+ print(" For platform-specific guides run:");
450
+ print(colors.cyan(" nova deploy --provider=railway"));
451
+ print(colors.cyan(" nova deploy --provider=docker"));
452
+ print(colors.cyan(" nova deploy --provider=vps"));
453
+ }
454
+ function printRailway() {
455
+ print(colors.bold(" Railway"));
456
+ print("");
457
+ printStep("Install Railway CLI: npm i -g @railway/cli");
458
+ printStep("Login: railway login");
459
+ printStep("Create project: railway init");
460
+ printStep("Set secrets: railway variables set BOT_TOKEN=xxx SERVER_URL=xxx");
461
+ printStep("Deploy: railway up");
462
+ print("");
463
+ print(" Nova bot will auto-restart on crashes (Railway default).");
464
+ }
465
+ function printDocker() {
466
+ print(colors.bold(" Docker"));
467
+ print("");
468
+ printStep("Build image: docker build -t my-nova-bot .");
469
+ printStep("Run: docker run -e BOT_TOKEN=xxx -e SERVER_URL=xxx my-nova-bot");
470
+ print("");
471
+ print(" Sample Dockerfile:");
472
+ print(colors.dim([
473
+ " FROM node:20-alpine",
474
+ " WORKDIR /app",
475
+ " COPY package*.json ./",
476
+ " RUN npm ci --omit=dev",
477
+ " COPY dist/ ./dist/",
478
+ ' CMD ["node", "dist/index.js"]'
479
+ ].join("\n")));
480
+ }
481
+ function printVps() {
482
+ print(colors.bold(" VPS / bare metal"));
483
+ print("");
484
+ printStep("Copy dist/ and package.json to the server");
485
+ printStep("npm ci --omit=dev");
486
+ printStep("Set BOT_TOKEN and SERVER_URL in your environment / .env");
487
+ printStep("Use PM2 for process management:");
488
+ print("");
489
+ print(colors.dim([
490
+ " npm install -g pm2",
491
+ " pm2 start dist/index.js --name my-nova-bot",
492
+ " pm2 save",
493
+ " pm2 startup # persist across reboots"
494
+ ].join("\n")));
495
+ }
496
+
497
+ // src/cli/commands/dev.ts
498
+ import * as cp3 from "child_process";
499
+ import * as fs2 from "fs";
500
+ import * as path4 from "path";
501
+ async function runDev(args, flags) {
502
+ const entry = typeof flags["entry"] === "string" ? flags["entry"] : detectEntry();
503
+ const debounce = typeof flags["debounce"] === "string" ? parseInt(flags["debounce"], 10) : 250;
504
+ const noWatch = flags["no-watch"] === true;
505
+ if (!fs2.existsSync(entry)) {
506
+ fatal(
507
+ `Entry file not found: ${entry}
508
+ Specify one with --entry src/index.ts or --entry dist/index.js`
509
+ );
510
+ }
511
+ printHeader("Nova dev mode");
512
+ printStep(`Entry: ${entry}`);
513
+ printStep(`Watch: ${noWatch ? "disabled" : "enabled"}`);
514
+ print("");
515
+ if (noWatch) {
516
+ spawnOnce(entry);
517
+ return;
518
+ }
519
+ const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
520
+ const useNodeWatch = nodeVersion >= 18 && entry.endsWith(".js");
521
+ if (useNodeWatch) {
522
+ spawnWithNodeWatch(entry);
523
+ } else {
524
+ await spawnWithHotLoader(entry, debounce);
525
+ }
526
+ }
527
+ function spawnOnce(entry) {
528
+ printInfo("Starting bot (no watch)\u2026");
529
+ const child = cp3.spawn(process.execPath, [entry], {
530
+ stdio: "inherit",
531
+ env: { ...process.env }
532
+ });
533
+ child.on("close", (code) => {
534
+ print(`
535
+ Bot exited with code ${code ?? 0}`);
536
+ process.exit(code ?? 0);
537
+ });
538
+ child.on("error", (err) => {
539
+ throw err;
540
+ });
541
+ }
542
+ function spawnWithNodeWatch(entry) {
543
+ printInfo("Starting bot with node --watch (Node 18+ built-in)\u2026");
544
+ printStep("Press Ctrl+C to stop");
545
+ print("");
546
+ const child = cp3.spawn(process.execPath, ["--watch", entry], {
547
+ stdio: "inherit",
548
+ env: { ...process.env }
549
+ });
550
+ child.on("error", (err) => {
551
+ throw err;
552
+ });
553
+ process.on("SIGINT", () => {
554
+ child.kill();
555
+ process.exit(0);
556
+ });
557
+ process.on("SIGTERM", () => {
558
+ child.kill();
559
+ process.exit(0);
560
+ });
561
+ }
562
+ async function spawnWithHotLoader(entry, debounce) {
563
+ const srcDir = path4.resolve("src");
564
+ if (!fs2.existsSync(srcDir)) {
565
+ printWarning("src/ directory not found \u2014 watching project root");
566
+ }
567
+ const watchDir = fs2.existsSync(srcDir) ? srcDir : process.cwd();
568
+ printInfo(`Watching ${watchDir} for changes\u2026`);
569
+ printStep("Press Ctrl+C to stop");
570
+ print("");
571
+ let current = null;
572
+ function spawnBot() {
573
+ if (current) {
574
+ current.kill();
575
+ current = null;
576
+ }
577
+ printStep("Starting bot\u2026");
578
+ current = cp3.spawn(process.execPath, [entry], {
579
+ stdio: "inherit",
580
+ env: { ...process.env }
581
+ });
582
+ current.on("close", (code) => {
583
+ if (code !== null && code !== 0) {
584
+ printWarning(`Bot exited with code ${code} \u2014 waiting for changes\u2026`);
585
+ }
586
+ });
587
+ current.on("error", (err) => {
588
+ printWarning(`Spawn error: ${err.message}`);
589
+ });
590
+ }
591
+ spawnBot();
592
+ const loader = new HotLoader(watchDir, { debounce });
593
+ loader.on("reload", (changed) => {
594
+ printInfo(`Reloading (${path4.relative(process.cwd(), changed)})`);
595
+ spawnBot();
596
+ });
597
+ loader.on("error", (err) => {
598
+ printWarning(`Watcher error: ${err.message}`);
599
+ });
600
+ loader.start();
601
+ await new Promise((resolve4) => {
602
+ process.on("SIGINT", () => {
603
+ loader.stop();
604
+ current?.kill();
605
+ resolve4();
606
+ });
607
+ process.on("SIGTERM", () => {
608
+ loader.stop();
609
+ current?.kill();
610
+ resolve4();
611
+ });
612
+ });
613
+ }
614
+ function detectEntry() {
615
+ const candidates = ["dist/index.js", "src/index.ts", "index.js", "index.ts"];
616
+ for (const c2 of candidates) {
617
+ if (fs2.existsSync(path4.resolve(c2))) {
618
+ if (c2.endsWith(".ts")) {
619
+ printWarning(
620
+ `Auto-detected TypeScript entry (${c2}).
621
+ Note: nova dev runs compiled JS. Run "npm run build" first, or
622
+ use ts-node / tsx: nova dev --entry dist/index.js`
623
+ );
624
+ }
625
+ return path4.resolve(c2);
626
+ }
627
+ }
628
+ return path4.resolve("dist/index.js");
629
+ }
630
+
631
+ // src/cli/runner.ts
632
+ var VERSION = "1.4.1";
633
+ async function run(argv) {
634
+ const parsed = parseArgs(argv);
635
+ const { command, positional, flags } = parsed;
636
+ if (flags["version"] || flags["v"]) {
637
+ print(`nova ${VERSION}`);
638
+ return;
639
+ }
640
+ if (!command || command === "help" || flags["help"] || flags["h"]) {
641
+ printHelp();
642
+ return;
643
+ }
644
+ try {
645
+ switch (command) {
646
+ case "new":
647
+ await runNew(positional, flags);
648
+ break;
649
+ case "generate":
650
+ await runGenerate(positional, flags);
651
+ break;
652
+ case "deploy":
653
+ await runDeploy(positional, flags);
654
+ break;
655
+ case "dev":
656
+ await runDev(positional, flags);
657
+ break;
658
+ default:
659
+ fatal(`Unknown command "${command}". Run "nova help" for usage.`);
660
+ }
661
+ } catch (err) {
662
+ const msg = err instanceof Error ? err.message : String(err);
663
+ fatal(msg);
664
+ }
665
+ }
666
+ function printHelp() {
667
+ print("");
668
+ print(colors.bold(` Nova CLI v${VERSION}`));
669
+ print(colors.dim(" The Nova Discord SDK command-line interface"));
670
+ print("");
671
+ print(colors.bold(" Usage"));
672
+ print(" nova <command> [options]");
673
+ print("");
674
+ print(colors.bold(" Commands"));
675
+ printTable(
676
+ [
677
+ ["new <name>", "Scaffold a new Nova bot project"],
678
+ ["generate <kind> <name>", "Generate a command, event, plugin, or middleware"],
679
+ ["dev", "Start the bot with hot reload"],
680
+ ["deploy", "Build and print deployment instructions"],
681
+ ["help", "Show this help message"]
682
+ ],
683
+ 30
684
+ );
685
+ print("");
686
+ print(colors.bold(" Generate kinds"));
687
+ printTable(
688
+ [
689
+ ["command", "Slash / prefix command handler"],
690
+ ["event", "Gateway event handler"],
691
+ ["plugin", "Reusable plugin"],
692
+ ["middleware", "Command middleware"]
693
+ ],
694
+ 14
695
+ );
696
+ print("");
697
+ print(colors.bold(" Examples"));
698
+ print(colors.dim(" nova new my-bot"));
699
+ print(colors.dim(" nova generate command ban"));
700
+ print(colors.dim(" nova generate event messageDelete"));
701
+ print(colors.dim(" nova dev --entry dist/index.js"));
702
+ print(colors.dim(" nova deploy --provider=railway"));
703
+ print("");
704
+ print(colors.bold(" Options"));
705
+ printTable(
706
+ [
707
+ ["--version, -v", "Print version number"],
708
+ ["--help, -h", "Print this help text"]
709
+ ],
710
+ 20
711
+ );
712
+ print("");
713
+ }
714
+ export {
715
+ run
716
+ };