0nmcp 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -12,6 +12,8 @@
12
12
  * npx 0nmcp connect Interactive connection setup
13
13
  * npx 0nmcp list List connected services
14
14
  * npx 0nmcp migrate Migrate from ~/.0nmcp to ~/.0n
15
+ * npx 0nmcp engine Engine commands (import, verify, platforms, export, open)
16
+ * npx 0nmcp app Application commands (run, build, inspect, validate, list)
15
17
  *
16
18
  * ═══════════════════════════════════════════════════════════════════════════
17
19
  */
@@ -78,6 +80,22 @@ ${c.bright}Usage:${c.reset}
78
80
  ${c.cyan}npx 0nmcp list${c.reset} List connected services
79
81
  ${c.cyan}npx 0nmcp migrate${c.reset} Migrate from ~/.0nmcp to ~/.0n
80
82
 
83
+ ${c.bright}Engine commands:${c.reset}
84
+
85
+ ${c.cyan}npx 0nmcp engine import <file>${c.reset} Import credentials from .env/CSV/JSON
86
+ ${c.cyan}npx 0nmcp engine verify${c.reset} Verify all connected API keys
87
+ ${c.cyan}npx 0nmcp engine platforms${c.reset} Generate AI platform configs
88
+ ${c.cyan}npx 0nmcp engine export${c.reset} Export .0n bundle (AI Brain)
89
+ ${c.cyan}npx 0nmcp engine open <bundle>${c.reset} Open/inspect a .0n bundle
90
+
91
+ ${c.bright}Application commands:${c.reset}
92
+
93
+ ${c.cyan}npx 0nmcp app run <file>${c.reset} Start application server
94
+ ${c.cyan}npx 0nmcp app build${c.reset} Build application bundle
95
+ ${c.cyan}npx 0nmcp app inspect <file>${c.reset} Show application metadata
96
+ ${c.cyan}npx 0nmcp app validate <file>${c.reset} Validate application structure
97
+ ${c.cyan}npx 0nmcp app list${c.reset} List installed applications
98
+
81
99
  ${c.bright}Serve options:${c.reset}
82
100
 
83
101
  ${c.cyan}npx 0nmcp serve --port 3000 --host 0.0.0.0${c.reset}
@@ -208,6 +226,20 @@ ${c.bright}Links:${c.reset}
208
226
  }
209
227
  }
210
228
 
229
+ // App
230
+ if (command === 'app') {
231
+ console.log(BANNER);
232
+ await handleApp(args.slice(1));
233
+ return;
234
+ }
235
+
236
+ // Engine
237
+ if (command === 'engine') {
238
+ console.log(BANNER);
239
+ await handleEngine(args.slice(1));
240
+ return;
241
+ }
242
+
211
243
  // Migrate
212
244
  if (command === 'migrate') {
213
245
  console.log(BANNER);
@@ -240,6 +272,7 @@ function initDotOn() {
240
272
  path.join(DOT_ON_DIR, 'snapshots'),
241
273
  path.join(DOT_ON_DIR, 'history'),
242
274
  path.join(DOT_ON_DIR, 'cache'),
275
+ path.join(DOT_ON_DIR, 'apps'),
243
276
  ];
244
277
 
245
278
  console.log(`${c.bright}Initializing ~/.0n directory...${c.reset}\n`);
@@ -421,4 +454,463 @@ async function interactiveConnect() {
421
454
  console.log(` Saved to: ${filePath}`);
422
455
  }
423
456
 
457
+ async function handleEngine(args) {
458
+ const sub = args[0];
459
+
460
+ if (!sub || sub === 'help') {
461
+ console.log(`${c.bright}Engine — .0n Conversion Engine${c.reset}\n`);
462
+ console.log(` ${c.cyan}import <file>${c.reset} Import credentials from .env, CSV, or JSON`);
463
+ console.log(` ${c.cyan}verify${c.reset} Verify all connected API keys`);
464
+ console.log(` ${c.cyan}platforms${c.reset} Generate AI platform configs`);
465
+ console.log(` ${c.cyan}export${c.reset} Export .0n bundle (AI Brain)`);
466
+ console.log(` ${c.cyan}open <bundle>${c.reset} Open/inspect a .0n bundle file\n`);
467
+ return;
468
+ }
469
+
470
+ if (sub === 'import') {
471
+ const source = args[1];
472
+ if (!source) {
473
+ console.log(`${c.red}Usage: npx 0nmcp engine import <file>${c.reset}`);
474
+ process.exit(1);
475
+ }
476
+ try {
477
+ const { parseFile } = await import('./engine/parser.js');
478
+ const { mapEnvVars, groupByService, validateMapping } = await import('./engine/mapper.js');
479
+ const { entries } = parseFile(source);
480
+ const { mapped, unmapped } = mapEnvVars(entries);
481
+ const groups = groupByService(mapped);
482
+
483
+ console.log(`${c.bright}Import Results:${c.reset}\n`);
484
+ console.log(` Entries found: ${entries.length}`);
485
+ console.log(` Mapped: ${mapped.length}`);
486
+ console.log(` Unmapped: ${unmapped.length}\n`);
487
+
488
+ console.log(`${c.bright}Services Detected:${c.reset}\n`);
489
+ for (const [service, group] of Object.entries(groups)) {
490
+ const validation = validateMapping(service, group.credentials);
491
+ const status = validation.valid ? `${c.green}complete${c.reset}` : `${c.yellow}missing: ${validation.missing.join(', ')}${c.reset}`;
492
+ console.log(` ${c.green}●${c.reset} ${c.bright}${service}${c.reset} — ${Object.keys(group.credentials).length} credentials (${status})`);
493
+ }
494
+
495
+ if (unmapped.length > 0) {
496
+ console.log(`\n${c.yellow}Unmapped variables:${c.reset} ${unmapped.map(u => u.key).join(', ')}`);
497
+ }
498
+
499
+ console.log(`\n${c.bright}Next:${c.reset} Run ${c.cyan}npx 0nmcp engine export${c.reset} to create a portable .0n bundle.`);
500
+ } catch (err) {
501
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
502
+ process.exit(1);
503
+ }
504
+ return;
505
+ }
506
+
507
+ if (sub === 'verify') {
508
+ try {
509
+ const { verifyAll } = await import('./engine/validator.js');
510
+ const { existsSync, readdirSync, readFileSync } = await import('fs');
511
+ const connectionsDir = path.join(DOT_ON_DIR, 'connections');
512
+
513
+ if (!existsSync(connectionsDir)) {
514
+ console.log(`${c.yellow}No connections found. Run ${c.cyan}npx 0nmcp connect${c.reset} first.`);
515
+ return;
516
+ }
517
+
518
+ const files = readdirSync(connectionsDir).filter(f => f.endsWith('.0n') || f.endsWith('.0n.json'));
519
+ const connections = {};
520
+ for (const file of files) {
521
+ try {
522
+ const data = JSON.parse(readFileSync(path.join(connectionsDir, file), 'utf8'));
523
+ if (data.$0n?.sealed) continue;
524
+ connections[data.service] = { credentials: data.auth?.credentials || {} };
525
+ } catch { /* skip */ }
526
+ }
527
+
528
+ if (Object.keys(connections).length === 0) {
529
+ console.log(`${c.yellow}No unsealed connections to verify.${c.reset}`);
530
+ return;
531
+ }
532
+
533
+ console.log(`${c.bright}Verifying ${Object.keys(connections).length} connections...${c.reset}\n`);
534
+
535
+ const { results, summary } = await verifyAll(connections);
536
+ for (const [service, result] of Object.entries(results)) {
537
+ const icon = result.valid ? `${c.green}✓` : `${c.red}✗`;
538
+ const latency = result.latency_ms ? ` (${result.latency_ms}ms)` : '';
539
+ console.log(` ${icon}${c.reset} ${c.bright}${service}${c.reset}${latency}${result.error ? ` — ${result.error}` : ''}`);
540
+ }
541
+
542
+ console.log(`\n${c.bright}Summary:${c.reset} ${summary.valid}/${summary.total} valid`);
543
+ } catch (err) {
544
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
545
+ process.exit(1);
546
+ }
547
+ return;
548
+ }
549
+
550
+ if (sub === 'platforms') {
551
+ try {
552
+ const { getPlatformInfo, generatePlatformConfig } = await import('./engine/platforms.js');
553
+ const platform = args[1];
554
+
555
+ if (platform) {
556
+ const config = generatePlatformConfig(platform);
557
+ console.log(`${c.bright}${config.name} Configuration:${c.reset}\n`);
558
+ console.log(` Config path: ${config.path || '(HTTP only)'}`);
559
+ console.log(` Format: ${config.format}\n`);
560
+ console.log(typeof config.config === 'string' ? config.config : JSON.stringify(config.config, null, 2));
561
+ } else {
562
+ const info = getPlatformInfo();
563
+ console.log(`${c.bright}Supported AI Platforms:${c.reset}\n`);
564
+ for (const p of info) {
565
+ const status = p.installed ? `${c.green}installed${c.reset}` : `${c.yellow}not installed${c.reset}`;
566
+ console.log(` ${p.installed ? c.green + '●' : c.blue + '○'}${c.reset} ${c.bright}${p.name}${c.reset} (${status})`);
567
+ if (p.configPath) console.log(` ${p.configPath}`);
568
+ }
569
+ console.log(`\n${c.bright}Tip:${c.reset} Run ${c.cyan}npx 0nmcp engine platforms <name>${c.reset} to see config for a specific platform.`);
570
+ console.log(` Names: claude_desktop, cursor, windsurf, gemini, continue, cline, openai`);
571
+ }
572
+ } catch (err) {
573
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
574
+ process.exit(1);
575
+ }
576
+ return;
577
+ }
578
+
579
+ if (sub === 'export') {
580
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
581
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
582
+
583
+ try {
584
+ const passphrase = await ask('Bundle passphrase: ');
585
+ if (!passphrase) {
586
+ console.log(`${c.red}Passphrase required.${c.reset}`);
587
+ rl.close();
588
+ process.exit(1);
589
+ }
590
+ const name = await ask(`Bundle name (default: 0n AI Brain): `) || '0n AI Brain';
591
+ rl.close();
592
+
593
+ const { createBundle } = await import('./engine/bundler.js');
594
+ const { existsSync, readdirSync, readFileSync } = await import('fs');
595
+ const connectionsDir = path.join(DOT_ON_DIR, 'connections');
596
+
597
+ if (!existsSync(connectionsDir)) {
598
+ console.log(`${c.yellow}No connections found.${c.reset}`);
599
+ return;
600
+ }
601
+
602
+ const files = readdirSync(connectionsDir).filter(f => f.endsWith('.0n') || f.endsWith('.0n.json'));
603
+ const connections = {};
604
+ for (const file of files) {
605
+ try {
606
+ const data = JSON.parse(readFileSync(path.join(connectionsDir, file), 'utf8'));
607
+ if (data.$0n?.sealed) continue;
608
+ connections[data.service] = {
609
+ credentials: data.auth?.credentials || {},
610
+ name: data.$0n?.name || data.service,
611
+ authType: data.auth?.type || 'api_key',
612
+ environment: data.environment || 'production',
613
+ };
614
+ } catch { /* skip */ }
615
+ }
616
+
617
+ if (Object.keys(connections).length === 0) {
618
+ console.log(`${c.yellow}No exportable connections found.${c.reset}`);
619
+ return;
620
+ }
621
+
622
+ console.log(`\n${c.bright}Creating AI Brain bundle...${c.reset}\n`);
623
+
624
+ const result = createBundle({ connections, passphrase, name, platforms: 'all' });
625
+
626
+ console.log(`${c.green}${c.bright}Bundle created!${c.reset}\n`);
627
+ console.log(` Path: ${result.path}`);
628
+ console.log(` Services: ${result.manifest.services.join(', ')}`);
629
+ console.log(` Connections: ${result.manifest.connection_count}`);
630
+ console.log(` Platforms: ${result.manifest.platform_count}`);
631
+ console.log(` Encryption: ${result.manifest.encryption.method}`);
632
+ console.log(`\n${c.bright}Share this file — recipient opens with:${c.reset} ${c.cyan}npx 0nmcp engine open <bundle>${c.reset}`);
633
+ } catch (err) {
634
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
635
+ process.exit(1);
636
+ }
637
+ return;
638
+ }
639
+
640
+ if (sub === 'open') {
641
+ const bundlePath = args[1];
642
+ if (!bundlePath) {
643
+ console.log(`${c.red}Usage: npx 0nmcp engine open <bundle.0n>${c.reset}`);
644
+ process.exit(1);
645
+ }
646
+
647
+ try {
648
+ const { inspectBundle, openBundle } = await import('./engine/bundler.js');
649
+
650
+ // First inspect
651
+ const info = inspectBundle(bundlePath);
652
+ console.log(`${c.bright}Bundle: ${info.name}${c.reset}\n`);
653
+ console.log(` Created: ${info.created}`);
654
+ console.log(` Services: ${info.services.map(s => s.service).join(', ')}`);
655
+ console.log(` Platforms: ${info.platforms.join(', ')}`);
656
+ if (info.includes.length > 0) {
657
+ console.log(` Includes: ${info.includes.map(i => i.name).join(', ')}`);
658
+ }
659
+
660
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
661
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
662
+
663
+ const passphrase = await ask('\nPassphrase to decrypt (leave empty to inspect only): ');
664
+ rl.close();
665
+
666
+ if (!passphrase) {
667
+ console.log(`\n${c.yellow}Inspect only — no credentials extracted.${c.reset}`);
668
+ return;
669
+ }
670
+
671
+ const result = openBundle(bundlePath, passphrase);
672
+
673
+ console.log(`\n${c.green}${c.bright}Bundle opened!${c.reset}\n`);
674
+ console.log(` Connections imported: ${result.connections.join(', ')}`);
675
+ if (result.includes.length > 0) {
676
+ console.log(` Files extracted: ${result.includes.join(', ')}`);
677
+ }
678
+ if (result.errors.length > 0) {
679
+ console.log(`\n${c.red}Errors:${c.reset}`);
680
+ for (const err of result.errors) {
681
+ console.log(` ${c.red}●${c.reset} ${err}`);
682
+ }
683
+ }
684
+ console.log(`\n${c.bright}Tip:${c.reset} Run ${c.cyan}npx 0nmcp engine platforms${c.reset} to install AI platform configs.`);
685
+ } catch (err) {
686
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
687
+ process.exit(1);
688
+ }
689
+ return;
690
+ }
691
+
692
+ console.log(`${c.red}Unknown engine command: ${sub}${c.reset}`);
693
+ console.log(`Run ${c.cyan}npx 0nmcp engine help${c.reset} for usage`);
694
+ process.exit(1);
695
+ }
696
+
697
+ async function handleApp(args) {
698
+ const sub = args[0];
699
+
700
+ if (!sub || sub === 'help') {
701
+ console.log(`${c.bright}Application Engine — Build & Run .0n Applications${c.reset}\n`);
702
+ console.log(` ${c.cyan}run <file>${c.reset} Start application server`);
703
+ console.log(` ${c.cyan}build${c.reset} Build application bundle`);
704
+ console.log(` ${c.cyan}inspect <file>${c.reset} Show application metadata`);
705
+ console.log(` ${c.cyan}validate <file>${c.reset} Validate application structure`);
706
+ console.log(` ${c.cyan}list${c.reset} List installed applications\n`);
707
+ return;
708
+ }
709
+
710
+ if (sub === 'run') {
711
+ const appFile = args[1];
712
+ if (!appFile) {
713
+ console.log(`${c.red}Usage: npx 0nmcp app run <file.0n> [--port 3000] [--passphrase <pw>]${c.reset}`);
714
+ process.exit(1);
715
+ }
716
+
717
+ const port = Number(getFlag(args, '--port', 3000));
718
+ let passphrase = getFlag(args, '--passphrase', null);
719
+
720
+ if (!passphrase) {
721
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
722
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
723
+ passphrase = await ask('Application passphrase: ');
724
+ rl.close();
725
+ }
726
+
727
+ if (!passphrase) {
728
+ console.log(`${c.red}Passphrase required.${c.reset}`);
729
+ process.exit(1);
730
+ }
731
+
732
+ try {
733
+ const { openApplication } = await import('./engine/app-builder.js');
734
+ const { Application } = await import('./engine/application.js');
735
+ const { ApplicationServer } = await import('./engine/app-server.js');
736
+
737
+ const bundle = openApplication(appFile, passphrase);
738
+ const application = new Application(bundle, { passphrase });
739
+ const server = new ApplicationServer(application);
740
+
741
+ await server.start({ port });
742
+
743
+ // Keep alive
744
+ process.on('SIGINT', async () => {
745
+ console.log(`\n${c.yellow}Shutting down...${c.reset}`);
746
+ await server.stop();
747
+ process.exit(0);
748
+ });
749
+ } catch (err) {
750
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
751
+ process.exit(1);
752
+ }
753
+ return;
754
+ }
755
+
756
+ if (sub === 'build') {
757
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
758
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
759
+
760
+ try {
761
+ const appName = await ask('Application name: ') || '0n Application';
762
+ const passphrase = await ask('Bundle passphrase: ');
763
+ if (!passphrase) {
764
+ console.log(`${c.red}Passphrase required.${c.reset}`);
765
+ rl.close();
766
+ process.exit(1);
767
+ }
768
+ rl.close();
769
+
770
+ const { createApplication } = await import('./engine/app-builder.js');
771
+
772
+ // Load local connections
773
+ const connectionsDir = path.join(DOT_ON_DIR, 'connections');
774
+ const connections = {};
775
+ if (fs.existsSync(connectionsDir)) {
776
+ const files = fs.readdirSync(connectionsDir).filter(f => f.endsWith('.0n') || f.endsWith('.0n.json'));
777
+ for (const file of files) {
778
+ try {
779
+ const data = JSON.parse(fs.readFileSync(path.join(connectionsDir, file), 'utf8'));
780
+ if (data.$0n?.sealed) continue;
781
+ connections[data.service] = {
782
+ credentials: data.auth?.credentials || {},
783
+ name: data.$0n?.name || data.service,
784
+ authType: data.auth?.type || 'api_key',
785
+ environment: data.environment || 'production',
786
+ };
787
+ } catch { /* skip */ }
788
+ }
789
+ }
790
+
791
+ // Load local workflows
792
+ const { loadLocalWorkflows } = await import('./engine/index.js');
793
+ const workflows = loadLocalWorkflows();
794
+
795
+ console.log(`\n${c.bright}Building application...${c.reset}\n`);
796
+
797
+ const result = createApplication({
798
+ name: appName,
799
+ passphrase,
800
+ connections,
801
+ workflows,
802
+ });
803
+
804
+ console.log(`${c.green}${c.bright}Application built!${c.reset}\n`);
805
+ console.log(` Path: ${result.path}`);
806
+ console.log(` Connections: ${result.manifest.connection_count}`);
807
+ console.log(` Workflows: ${result.manifest.workflow_count}`);
808
+ console.log(`\n${c.bright}Run with:${c.reset} ${c.cyan}npx 0nmcp app run ${result.path}${c.reset}`);
809
+ } catch (err) {
810
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
811
+ process.exit(1);
812
+ }
813
+ return;
814
+ }
815
+
816
+ if (sub === 'inspect') {
817
+ const appFile = args[1];
818
+ if (!appFile) {
819
+ console.log(`${c.red}Usage: npx 0nmcp app inspect <file.0n>${c.reset}`);
820
+ process.exit(1);
821
+ }
822
+
823
+ try {
824
+ const { inspectApplication } = await import('./engine/app-builder.js');
825
+ const info = inspectApplication(appFile);
826
+
827
+ console.log(`${c.bright}Application: ${info.name}${c.reset}\n`);
828
+ console.log(` Version: ${info.version}`);
829
+ console.log(` Author: ${info.author || '—'}`);
830
+ console.log(` Created: ${info.created}`);
831
+ if (info.description) console.log(` Description: ${info.description}`);
832
+ console.log(`\n Connections: ${info.connections.map(c2 => c2.service).join(', ') || 'none'}`);
833
+ console.log(` Workflows: ${info.workflows.join(', ') || 'none'}`);
834
+ console.log(` Operations: ${info.operations.join(', ') || 'none'}`);
835
+ console.log(` Endpoints: ${info.endpoints.join(', ') || 'none'}`);
836
+ console.log(` Automations: ${info.automations.join(', ') || 'none'}`);
837
+ } catch (err) {
838
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
839
+ process.exit(1);
840
+ }
841
+ return;
842
+ }
843
+
844
+ if (sub === 'validate') {
845
+ const appFile = args[1];
846
+ if (!appFile) {
847
+ console.log(`${c.red}Usage: npx 0nmcp app validate <file.0n>${c.reset}`);
848
+ process.exit(1);
849
+ }
850
+
851
+ try {
852
+ const { validateApplication } = await import('./engine/app-builder.js');
853
+ const data = JSON.parse(fs.readFileSync(appFile, 'utf8'));
854
+ const result = validateApplication(data);
855
+
856
+ if (result.valid) {
857
+ console.log(`${c.green}${c.bright}Application is valid!${c.reset}`);
858
+ } else {
859
+ console.log(`${c.red}${c.bright}Validation failed:${c.reset}\n`);
860
+ for (const err of result.errors) {
861
+ console.log(` ${c.red}●${c.reset} ${err}`);
862
+ }
863
+ }
864
+
865
+ if (result.warnings?.length > 0) {
866
+ console.log(`\n${c.yellow}Warnings:${c.reset}`);
867
+ for (const w of result.warnings) {
868
+ console.log(` ${c.yellow}○${c.reset} ${w}`);
869
+ }
870
+ }
871
+ } catch (err) {
872
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
873
+ process.exit(1);
874
+ }
875
+ return;
876
+ }
877
+
878
+ if (sub === 'list') {
879
+ const appsDir = path.join(DOT_ON_DIR, 'apps');
880
+ if (!fs.existsSync(appsDir)) {
881
+ console.log(`${c.yellow}No applications installed.${c.reset}`);
882
+ return;
883
+ }
884
+
885
+ const files = fs.readdirSync(appsDir).filter(f => f.endsWith('.0n') || f.endsWith('.0n.json'));
886
+ if (files.length === 0) {
887
+ console.log(`${c.yellow}No applications installed.${c.reset}`);
888
+ return;
889
+ }
890
+
891
+ console.log(`${c.bright}Installed Applications:${c.reset}\n`);
892
+
893
+ for (const file of files) {
894
+ try {
895
+ const data = JSON.parse(fs.readFileSync(path.join(appsDir, file), 'utf8'));
896
+ if (data.$0n?.type !== 'application') continue;
897
+
898
+ const wfCount = Object.keys(data.workflows || {}).length;
899
+ const epCount = Object.keys(data.endpoints || {}).length;
900
+ console.log(` ${c.green}●${c.reset} ${c.bright}${data.$0n.name || file}${c.reset} v${data.$0n.version || '1.0.0'}`);
901
+ console.log(` ${wfCount} workflows, ${epCount} endpoints`);
902
+ console.log(` ${file}`);
903
+ console.log('');
904
+ } catch {
905
+ console.log(` ${c.red}●${c.reset} ${file} (error reading)`);
906
+ }
907
+ }
908
+ return;
909
+ }
910
+
911
+ console.log(`${c.red}Unknown app command: ${sub}${c.reset}`);
912
+ console.log(`Run ${c.cyan}npx 0nmcp app help${c.reset} for usage`);
913
+ process.exit(1);
914
+ }
915
+
424
916
  main().catch(console.error);
package/connections.js CHANGED
@@ -18,6 +18,7 @@ const SNAPSHOTS_DIR = join(DOT_ON, "snapshots");
18
18
  const HISTORY_DIR = join(DOT_ON, "history");
19
19
  const CACHE_DIR = join(DOT_ON, "cache");
20
20
  const PLUGINS_DIR = join(DOT_ON, "plugins");
21
+ const APPS_DIR = join(DOT_ON, "apps");
21
22
  const CONFIG_FILE = join(DOT_ON, "config.json");
22
23
 
23
24
  // Legacy path for migration
@@ -28,7 +29,7 @@ const LEGACY_FILE = join(LEGACY_DIR, "connections.json");
28
29
  * Initialize the ~/.0n/ directory structure.
29
30
  */
30
31
  export function initDotOn() {
31
- const dirs = [DOT_ON, CONNECTIONS_DIR, WORKFLOWS_DIR, SNAPSHOTS_DIR, HISTORY_DIR, CACHE_DIR, PLUGINS_DIR];
32
+ const dirs = [DOT_ON, CONNECTIONS_DIR, WORKFLOWS_DIR, SNAPSHOTS_DIR, HISTORY_DIR, CACHE_DIR, PLUGINS_DIR, APPS_DIR];
32
33
  for (const dir of dirs) {
33
34
  if (!existsSync(dir)) {
34
35
  mkdirSync(dir, { recursive: true });
@@ -331,3 +332,4 @@ export const CONNECTIONS_PATH = CONNECTIONS_DIR;
331
332
  export const HISTORY_PATH = HISTORY_DIR;
332
333
  export const WORKFLOWS_PATH = WORKFLOWS_DIR;
333
334
  export const SNAPSHOTS_PATH = SNAPSHOTS_DIR;
335
+ export const APPS_PATH = APPS_DIR;