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 +492 -0
- package/connections.js +3 -1
- package/engine/app-builder.js +318 -0
- package/engine/app-server.js +471 -0
- package/engine/application.js +205 -0
- package/engine/bundler.js +408 -0
- package/engine/cipher-portable.js +94 -0
- package/engine/index.js +668 -0
- package/engine/mapper.js +292 -0
- package/engine/operations.js +227 -0
- package/engine/parser.js +221 -0
- package/engine/platforms.js +254 -0
- package/engine/scheduler.js +270 -0
- package/engine/validator.js +257 -0
- package/index.js +8 -1
- package/lib/badges.json +1 -1
- package/lib/stats.json +5 -3
- package/package.json +30 -5
- package/server.js +4 -2
- package/workflow.js +36 -4
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;
|