library.dr-conversion 0.2.9 → 0.3.1

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.
Files changed (3) hide show
  1. package/README.md +28 -2
  2. package/cli.js +487 -5
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm version](https://badge.fury.io/js/library.dr-conversion.svg)](https://www.npmjs.com/package/library.dr-conversion)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- **Library.DR-Conversion** (v0.2.8) is a TypeScript library that provides a unified, platform-agnostic interface for building chat bots that work across multiple platforms like Discord, Root, and potentially others. Write your bot logic once, and deploy it anywhere!
8
+ **Library.DR-Conversion** (v0.3.1) is a TypeScript library that provides a unified, platform-agnostic interface for building chat bots that work across multiple platforms like Discord, Root, and potentially others. Write your bot logic once, and deploy it anywhere!
9
9
 
10
10
  ## ✨ Features
11
11
 
@@ -79,6 +79,28 @@ npx library.dr-conversion init -p root-app -n my-root-app
79
79
 
80
80
  This creates a fully configured project with TypeScript, example code, and environment variables ready to go!
81
81
 
82
+ **Generate Root Bot Manifest:**
83
+
84
+ If you're building a Root bot, you'll need a `root-manifest.json` file:
85
+
86
+ ```bash
87
+ # Quick generation with defaults
88
+ npx library.dr-conversion generate-manifest
89
+ # or simply
90
+ npm run generate-manifest
91
+
92
+ # Interactive mode with guided prompts
93
+ npx library.dr-conversion generate-manifest -i
94
+ ```
95
+
96
+ The generator will:
97
+ - Create a proper UUID for your bot ID
98
+ - Guide you through version and configuration options
99
+ - Set up permissions for your bot
100
+ - Generate a valid `root-manifest.json` file
101
+
102
+ See the [Root Bot Manifest Guide](docs/ROOT_APP_MANIFEST.md) for details.
103
+
82
104
  ### Basic Usage
83
105
 
84
106
  ```typescript
@@ -273,11 +295,14 @@ await rootBot.connect();
273
295
 
274
296
  #### Building Root Apps with This Library
275
297
 
276
- **Prerequisites**: First install the Root client SDK:
298
+ **Prerequisites**:
277
299
  ```bash
300
+ # Install the Root client SDK
278
301
  npm install @rootsdk/client-app
279
302
  ```
280
303
 
304
+ **Note**: Root Apps (client-side) have different deployment requirements than Root Bots. They run in the Root client browser and don't require server-side manifest files. Configuration is done through the Root platform's app management interface.
305
+
281
306
  You can now create Root Apps (client-side) using the same unified interface:
282
307
 
283
308
  ```typescript
@@ -565,6 +590,7 @@ const client = new UnifiedClient({
565
590
  - šŸ“— **[API Reference](API.md)** - Detailed API documentation
566
591
  - šŸ“™ **[Migration Guide](docs/MIGRATION.md)** - Upgrade and migration guides
567
592
  - šŸ“• **[Contributing Guide](CONTRIBUTING.md)** - How to contribute
593
+ - šŸ“‹ **[Root Bot Manifest Guide](docs/ROOT_APP_MANIFEST.md)** - Creating root-manifest.json for Root Bots (server-side)
568
594
 
569
595
  ### Creating Rich Messages
570
596
 
package/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * CLI tool for Library.DR-Conversion
@@ -10,11 +10,96 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
  const { execSync } = require('child_process');
12
12
  const readline = require('readline');
13
+ const crypto = require('crypto');
14
+
15
+ // Generate a UUID v4
16
+ function generateUUID() {
17
+ return crypto.randomUUID();
18
+ }
19
+
20
+ // Detect platform from package.json dependencies
21
+ function detectPlatform() {
22
+ try {
23
+ const pkgPath = path.join(process.cwd(), 'package.json');
24
+ if (!fs.existsSync(pkgPath)) {
25
+ return null;
26
+ }
27
+
28
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
29
+ const allDeps = {
30
+ ...pkg.dependencies,
31
+ ...pkg.devDependencies,
32
+ ...pkg.peerDependencies
33
+ };
34
+
35
+ // Check for Root platforms
36
+ if (allDeps['@rootsdk/server-bot']) {
37
+ return 'root';
38
+ }
39
+ if (allDeps['@rootsdk/client-app']) {
40
+ return 'root-app';
41
+ }
42
+ if (allDeps['discord.js']) {
43
+ return 'discord';
44
+ }
45
+
46
+ return null;
47
+ } catch (error) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ // Validate a root manifest object against minimal Root requirements
53
+ function validateManifestObject(manifest) {
54
+ const errors = [];
55
+ if (!manifest || typeof manifest !== 'object') {
56
+ errors.push('Manifest must be a JSON object');
57
+ return { valid: false, errors };
58
+ }
59
+
60
+ if (!manifest.id || typeof manifest.id !== 'string') {
61
+ errors.push('Missing or invalid "id" (string)');
62
+ }
63
+
64
+ if (!manifest.version || typeof manifest.version !== 'string') {
65
+ errors.push('Missing or invalid "version" (string)');
66
+ }
67
+
68
+ if (!manifest.package || typeof manifest.package !== 'object') {
69
+ errors.push('Missing "package" object');
70
+ } else {
71
+ if (manifest.package.server) {
72
+ const server = manifest.package.server;
73
+ if (!server.launch || typeof server.launch !== 'string') {
74
+ errors.push('package.server.launch is required and must be a string');
75
+ }
76
+ if (!Array.isArray(server.deploy) || server.deploy.length === 0) {
77
+ errors.push('package.server.deploy must be a non-empty array');
78
+ }
79
+ if (!Array.isArray(server.node_modules) || server.node_modules.length === 0) {
80
+ errors.push('package.server.node_modules must be a non-empty array');
81
+ }
82
+ }
83
+
84
+ if (manifest.package.client) {
85
+ const client = manifest.package.client;
86
+ // accept either entry or deploy
87
+ if (!client.entry && (!Array.isArray(client.deploy) || client.deploy.length === 0)) {
88
+ errors.push('package.client.entry or package.client.deploy is required');
89
+ }
90
+ if (client.assets && !Array.isArray(client.assets)) {
91
+ errors.push('package.client.assets must be an array if provided');
92
+ }
93
+ }
94
+ }
95
+
96
+ return { valid: errors.length === 0, errors };
97
+ }
13
98
 
14
99
  program
15
100
  .name('library.dr-conversion')
16
- .description('CLI tools for Library.DR-Conversion v0.2.8')
17
- .version('0.2.8');
101
+ .description('CLI tools for Library.DR-Conversion v0.2.9')
102
+ .version('0.2.9');
18
103
 
19
104
  program
20
105
  .command('init')
@@ -192,12 +277,128 @@ client.connect();
192
277
 
193
278
  fs.writeFileSync(path.join(projectDir, 'src', 'index.ts'), botCode);
194
279
 
280
+ // Create root-manifest.json for Root Bots
281
+ if (options.platform === 'root') {
282
+ const manifest = {
283
+ id: generateUUID(),
284
+ version: '1.0.0',
285
+ package: {
286
+ server: {
287
+ launch: 'dist/index.js',
288
+ deploy: ['dist'],
289
+ node_modules: ['node_modules']
290
+ }
291
+ },
292
+ settings: {
293
+ groups: []
294
+ },
295
+ permissions: {
296
+ community: {},
297
+ channel: {
298
+ CreateMessage: true,
299
+ ReadMessage: true
300
+ }
301
+ }
302
+ };
303
+
304
+ fs.writeFileSync(
305
+ path.join(projectDir, 'root-manifest.json'),
306
+ JSON.stringify(manifest, null, 2)
307
+ );
308
+
309
+ // Create README explaining manifest
310
+ const manifestReadme = `# Root Bot Manifest
311
+
312
+ Your bot's root-manifest.json defines how it integrates with Root.
313
+
314
+ ## Important:
315
+ - The 'id' field is unique to your bot - don't change it after publishing
316
+ - Update 'version' following semantic versioning when you make changes
317
+ - Add permissions your bot needs to 'permissions.community' or 'permissions.channel'
318
+ - See docs/ROOT_BOT_MANIFEST.md for full documentation
319
+
320
+ ## Publishing:
321
+ \`\`\`bash
322
+ npm run build
323
+ npx @rootsdk/cli publish
324
+ \`\`\`
325
+ `;
326
+ fs.writeFileSync(path.join(projectDir, 'MANIFEST.md'), manifestReadme);
327
+ }
328
+
329
+ // Create root-app-manifest.json for Root Apps (client-side)
330
+ if (options.platform === 'root-app') {
331
+ const appManifest = {
332
+ id: generateUUID(),
333
+ version: '1.0.0',
334
+ name: options.name,
335
+ description: `${options.name} - Root App`,
336
+ author: '',
337
+ homepage: '',
338
+ package: {
339
+ client: {
340
+ entry: 'dist/index.html',
341
+ assets: ['dist'],
342
+ node_modules: ['node_modules']
343
+ }
344
+ },
345
+ settings: {
346
+ ui: []
347
+ },
348
+ permissions: {
349
+ channel: {
350
+ CreateMessage: true,
351
+ ReadMessage: true
352
+ }
353
+ }
354
+ };
355
+
356
+ fs.writeFileSync(
357
+ path.join(projectDir, 'root-manifest.json'),
358
+ JSON.stringify(appManifest, null, 2)
359
+ );
360
+
361
+ const appManifestReadme = `# Root App Manifest
362
+
363
+ This project includes a generated root-manifest.json to help document your app.
364
+
365
+ This manifest follows the Root App manifest overview and provides the basic fields used by the Root platform:
366
+ - 'id': Unique UUID for the app
367
+ - 'version': Semver version
368
+ - 'package.client.entry': The client entry HTML for the app
369
+ - 'package.client.assets': Client assets to include in the upload
370
+ - 'permissions': Channel-level permissions used by the app
371
+
372
+ Notes:
373
+ - Update 'version' following semantic versioning when you make changes.
374
+ - Review permissions and remove any that aren't needed.
375
+ - Configure UI settings in the 'settings.ui' array (see Root docs for details):
376
+ https://docs.rootapp.com/docs/app-docs/configure/manifest-overview/
377
+
378
+ Deployment:
379
+ Build your app and follow the Root platform instructions to upload or register it.
380
+ `;
381
+
382
+ fs.writeFileSync(path.join(projectDir, 'MANIFEST.md'), appManifestReadme);
383
+ }
384
+
195
385
  console.log(`āœ… Created ${options.name}`);
196
386
  console.log(`\nšŸ“ Next steps:`);
197
387
  console.log(` cd ${options.name}`);
198
388
  console.log(` npm install`);
199
- console.log(` cp .env.example .env`);
200
- console.log(` # Edit .env with your bot token`);
389
+
390
+ if (options.platform === 'root') {
391
+ console.log(` cp .env.example .env`);
392
+ console.log(` # Edit .env with your bot token`);
393
+ console.log(` # Edit root-manifest.json - change the 'id' to a unique value`);
394
+ console.log(` # See MANIFEST.md for manifest documentation`);
395
+ } else if (options.platform === 'discord') {
396
+ console.log(` cp .env.example .env`);
397
+ console.log(` # Edit .env with your bot token`);
398
+ } else if (options.platform === 'root-app') {
399
+ console.log(` # Root Apps run in the Root client - no token needed`);
400
+ console.log(` # Configure your app in the Root platform`);
401
+ }
201
402
  console.log(` npm run dev`);
202
403
  });
203
404
 
@@ -226,6 +427,287 @@ program
226
427
  console.log('āœ… Configuration is valid');
227
428
  });
228
429
 
430
+ program
431
+ .command('validate-manifest')
432
+ .description('Validate root-manifest.json in the current folder')
433
+ .option('-f, --file <path>', 'Manifest file path', 'root-manifest.json')
434
+ .action((options) => {
435
+ const file = options.file || 'root-manifest.json';
436
+ if (!fs.existsSync(file)) {
437
+ console.error(`āŒ Manifest not found: ${file}`);
438
+ process.exit(1);
439
+ }
440
+
441
+ let raw;
442
+ try {
443
+ raw = fs.readFileSync(file, 'utf8');
444
+ } catch (err) {
445
+ console.error('āŒ Failed to read manifest:', err.message);
446
+ process.exit(1);
447
+ }
448
+
449
+ let manifest;
450
+ try {
451
+ manifest = JSON.parse(raw);
452
+ } catch (err) {
453
+ console.error('āŒ Manifest is not valid JSON:', err.message);
454
+ process.exit(1);
455
+ }
456
+
457
+ const result = validateManifestObject(manifest);
458
+ if (!result.valid) {
459
+ console.error('āŒ Manifest validation failed:');
460
+ for (const e of result.errors) console.error(' -', e);
461
+ process.exit(1);
462
+ }
463
+
464
+ console.log('āœ… Manifest is valid');
465
+ });
466
+
467
+ program
468
+ .command('generate-manifest')
469
+ .description('Generate a manifest file for Root Bots or Root Apps (auto-detects platform)')
470
+ .option('-i, --interactive', 'Interactive mode with prompts')
471
+ .option('-p, --platform <type>', 'Platform type (root, root-app) - auto-detected if not specified')
472
+ .action(async (options) => {
473
+ // Auto-detect platform if not specified
474
+ const platform = options.platform || detectPlatform();
475
+
476
+ if (!platform || (platform !== 'root' && platform !== 'root-app')) {
477
+ console.error('āŒ Could not detect Root platform.');
478
+ console.error(' Make sure you have @rootsdk/server-bot or @rootsdk/client-app installed,');
479
+ console.error(' or specify the platform: --platform root or --platform root-app');
480
+ process.exit(1);
481
+ }
482
+
483
+ const isRootBot = platform === 'root';
484
+ const manifestFile = 'root-manifest.json';
485
+
486
+ console.log(`\nšŸ” Detected platform: ${isRootBot ? 'Root Bot (server-side)' : 'Root App (client-side)'}`);
487
+
488
+ // Check if manifest already exists
489
+ if (fs.existsSync(manifestFile)) {
490
+ console.log(`āš ļø ${manifestFile} already exists in this directory.`);
491
+ if (!options.interactive) {
492
+ console.log('Use --interactive to configure a new one anyway.');
493
+ return;
494
+ }
495
+ }
496
+
497
+ let manifest;
498
+
499
+ if (isRootBot) {
500
+ // Root Bot manifest (server-side)
501
+ manifest = {
502
+ id: generateUUID(),
503
+ version: '1.0.0',
504
+ package: {
505
+ server: {
506
+ launch: 'dist/index.js',
507
+ deploy: ['dist'],
508
+ node_modules: ['node_modules']
509
+ }
510
+ },
511
+ settings: {
512
+ groups: []
513
+ },
514
+ permissions: {
515
+ community: {},
516
+ channel: {}
517
+ }
518
+ };
519
+ } else {
520
+ // Root App manifest (client-side)
521
+ manifest = {
522
+ id: generateUUID(),
523
+ version: '1.0.0',
524
+ name: '',
525
+ description: '',
526
+ author: '',
527
+ homepage: '',
528
+ package: {
529
+ client: {
530
+ entry: 'dist/index.html',
531
+ assets: ['dist'],
532
+ node_modules: ['node_modules']
533
+ }
534
+ },
535
+ settings: {
536
+ ui: []
537
+ },
538
+ permissions: {
539
+ channel: {}
540
+ }
541
+ };
542
+ }
543
+
544
+ if (options.interactive) {
545
+ const rl = readline.createInterface({
546
+ input: process.stdin,
547
+ output: process.stdout
548
+ });
549
+
550
+ function question(prompt) {
551
+ return new Promise((resolve) => {
552
+ rl.question(prompt, resolve);
553
+ });
554
+ }
555
+
556
+ console.log('\n╔════════════════════════════════════════════════════════╗');
557
+ console.log(`ā•‘ Root ${isRootBot ? 'Bot' : 'App'} Manifest Generator${' '.repeat(isRootBot ? 25 : 24)}ā•‘`);
558
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
559
+
560
+ // Version
561
+ const version = await question('Version (default: 1.0.0): ');
562
+ if (version.trim()) manifest.version = version.trim();
563
+
564
+ if (isRootBot) {
565
+ // Root Bot specific fields
566
+ // Launch file
567
+ const launch = await question('Launch file (default: dist/index.js): ');
568
+ if (launch.trim()) manifest.package.server.launch = launch.trim();
569
+
570
+ // Permissions
571
+ console.log('\nšŸ“‹ Bot Permissions:\n');
572
+ console.log('Community permissions:');
573
+ } else {
574
+ // Root App specific fields
575
+ const name = await question('App name: ');
576
+ if (name.trim()) manifest.name = name.trim();
577
+
578
+ const description = await question('App description: ');
579
+ if (description.trim()) manifest.description = description.trim();
580
+
581
+ const author = await question('Author: ');
582
+ if (author.trim()) manifest.author = author.trim();
583
+
584
+ const homepage = await question('Homepage URL (optional): ');
585
+ if (homepage.trim()) manifest.homepage = homepage.trim();
586
+
587
+ console.log('\nšŸ“‹ App Permissions:\n');
588
+ console.log('Channel permissions (Root Apps run in browser):');
589
+ }
590
+
591
+ if (isRootBot) {
592
+ // Community permissions only for Root Bots
593
+ console.log('Community permissions:');
594
+ const manageRoles = await question(' Need to manage roles? (y/n): ');
595
+ if (manageRoles.toLowerCase() === 'y') {
596
+ manifest.permissions.community.ManageRoles = true;
597
+ }
598
+
599
+ const manageCommunity = await question(' Need to manage community settings? (y/n): ');
600
+ if (manageCommunity.toLowerCase() === 'y') {
601
+ manifest.permissions.community.ManageCommunity = true;
602
+ }
603
+
604
+ const manageChannels = await question(' Need to manage channels? (y/n): ');
605
+ if (manageChannels.toLowerCase() === 'y') {
606
+ manifest.permissions.community.ManageChannels = true;
607
+ }
608
+
609
+ console.log('\nChannel permissions:');
610
+ }
611
+
612
+ // Channel permissions (both Root Bot and Root App)
613
+ const createMsg = await question(' Need to send messages? (y/n): ');
614
+ if (createMsg.toLowerCase() === 'y') {
615
+ manifest.permissions.channel.CreateMessage = true;
616
+ }
617
+
618
+ const readMsg = await question(' Need to read messages? (y/n): ');
619
+ if (readMsg.toLowerCase() === 'y') {
620
+ manifest.permissions.channel.ReadMessage = true;
621
+ }
622
+
623
+ const deleteMsg = await question(' Need to delete messages? (y/n): ');
624
+ if (deleteMsg.toLowerCase() === 'y') {
625
+ manifest.permissions.channel.DeleteMessage = true;
626
+ }
627
+
628
+ const managePins = await question(' Need to manage pins? (y/n): ');
629
+ if (managePins.toLowerCase() === 'y') {
630
+ manifest.permissions.channel.ManagePins = true;
631
+ }
632
+
633
+ rl.close();
634
+ }
635
+
636
+ // Write manifest file
637
+ fs.writeFileSync(
638
+ manifestFile,
639
+ JSON.stringify(manifest, null, 2)
640
+ );
641
+
642
+ console.log(`\nāœ… Generated ${manifestFile} successfully!\n`);
643
+ console.log('šŸ“‹ Manifest Details:');
644
+ console.log(` Platform: ${isRootBot ? 'Root Bot (server-side)' : 'Root App (client-side)'}`);
645
+ console.log(` ID: ${manifest.id}`);
646
+ console.log(` Version: ${manifest.version}`);
647
+ if (isRootBot) {
648
+ console.log(` Launch: ${manifest.package.server.launch}`);
649
+ } else {
650
+ console.log(` Name: ${manifest.name || '(not set)'}`);
651
+ }
652
+ console.log('');
653
+ console.log('āš ļø Important:');
654
+ console.log(' - The ID is unique to your ' + (isRootBot ? 'bot' : 'app') + ' - never change it after publishing');
655
+ console.log(' - Update version when you make changes (follow semver)');
656
+ if (isRootBot) {
657
+ console.log(' - See docs/ROOT_APP_MANIFEST.md for full documentation');
658
+ }
659
+ console.log('');
660
+ console.log('šŸ“ Next steps:');
661
+ console.log(` 1. Review and customize ${manifestFile}`);
662
+ if (isRootBot) {
663
+ console.log(' 2. Build your bot: npm run build');
664
+ console.log(' 3. Publish: npx @rootsdk/cli publish');
665
+ } else {
666
+ console.log(' 2. Build your app: npm run build');
667
+ console.log(' 3. Deploy through Root platform interface');
668
+ }
669
+ console.log('');
670
+ });
671
+
672
+ program
673
+ .command('publish')
674
+ .description('Validate and publish the current root-manifest.json using @rootsdk/cli')
675
+ .option('-f, --file <path>', 'Manifest file to publish', 'root-manifest.json')
676
+ .option('--no-validate', 'Skip manifest validation step')
677
+ .action((options) => {
678
+ const file = options.file || 'root-manifest.json';
679
+ if (!fs.existsSync(file)) {
680
+ console.error(`āŒ Manifest not found: ${file}`);
681
+ process.exit(1);
682
+ }
683
+
684
+ if (options.validate !== false) {
685
+ let manifest;
686
+ try {
687
+ manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
688
+ } catch (err) {
689
+ console.error('āŒ Failed to parse manifest:', err.message);
690
+ process.exit(1);
691
+ }
692
+
693
+ const result = validateManifestObject(manifest);
694
+ if (!result.valid) {
695
+ console.error('āŒ Manifest validation failed:');
696
+ for (const e of result.errors) console.error(' -', e);
697
+ process.exit(1);
698
+ }
699
+ console.log('āœ… Manifest validation passed');
700
+ }
701
+
702
+ try {
703
+ console.log('šŸ” Running publish via @rootsdk/cli...');
704
+ execSync('npx @rootsdk/cli publish', { stdio: 'inherit' });
705
+ } catch (err) {
706
+ console.error('āŒ Publish failed:', err.message);
707
+ process.exit(1);
708
+ }
709
+ });
710
+
229
711
  program
230
712
  .command('setup')
231
713
  .description('Interactive setup wizard to install only the dependencies you need')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "library.dr-conversion",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Unified interface for building multi-platform chat bots (Discord, Root, and more)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,6 +19,7 @@
19
19
  "typecheck": "tsc --noEmit",
20
20
  "dev": "tsc --watch",
21
21
  "setup": "node cli.js setup",
22
+ "generate-manifest": "node cli.js generate-manifest",
22
23
  "example:simple": "ts-node examples/simple-bot.ts",
23
24
  "example:discord": "ts-node examples/discord-bot.ts",
24
25
  "example:root": "ts-node examples/root-bot.ts",