0nmcp 1.6.0 → 2.0.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/README.md +24 -23
- package/cli.js +667 -1
- package/command-runner.js +224 -0
- package/commands.js +115 -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 +13 -0
- package/engine/index.js +281 -3
- package/engine/operations.js +227 -0
- package/engine/scheduler.js +270 -0
- package/index.js +8 -1
- package/lib/badges.json +1 -1
- package/lib/stats.json +4 -3
- package/package.json +45 -6
- package/server.js +2 -2
- package/vault/container.js +479 -0
- package/vault/crypto-container.js +278 -0
- package/vault/escrow.js +227 -0
- package/vault/layers.js +254 -0
- package/vault/registry.js +159 -0
- package/vault/seal.js +74 -0
- package/vault/tools-container.js +356 -0
- package/workflow.js +36 -4
package/cli.js
CHANGED
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* npx 0nmcp init Initialize ~/.0n directory
|
|
12
12
|
* npx 0nmcp connect Interactive connection setup
|
|
13
13
|
* npx 0nmcp list List connected services
|
|
14
|
+
* npx 0nmcp commands List named RUNs from SWITCH file
|
|
15
|
+
* npx 0nmcp shell Interactive REPL (type /command)
|
|
16
|
+
* npx 0nmcp <alias> Run a named command from SWITCH file
|
|
14
17
|
* npx 0nmcp migrate Migrate from ~/.0nmcp to ~/.0n
|
|
15
18
|
* npx 0nmcp engine Engine commands (import, verify, platforms, export, open)
|
|
19
|
+
* npx 0nmcp app Application commands (run, build, inspect, validate, list)
|
|
16
20
|
*
|
|
17
21
|
* ═══════════════════════════════════════════════════════════════════════════
|
|
18
22
|
*/
|
|
@@ -23,6 +27,8 @@ import { fileURLToPath } from 'url';
|
|
|
23
27
|
import fs from 'fs';
|
|
24
28
|
import os from 'os';
|
|
25
29
|
import readline from 'readline';
|
|
30
|
+
import { loadCommands, listCommands } from './commands.js';
|
|
31
|
+
import { runCommand } from './command-runner.js';
|
|
26
32
|
|
|
27
33
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
34
|
|
|
@@ -87,6 +93,40 @@ ${c.bright}Engine commands:${c.reset}
|
|
|
87
93
|
${c.cyan}npx 0nmcp engine export${c.reset} Export .0n bundle (AI Brain)
|
|
88
94
|
${c.cyan}npx 0nmcp engine open <bundle>${c.reset} Open/inspect a .0n bundle
|
|
89
95
|
|
|
96
|
+
${c.bright}Vault Container commands (Patent Pending #63/990,046):${c.reset}
|
|
97
|
+
|
|
98
|
+
${c.cyan}npx 0nmcp vault create${c.reset} Create a .0nv vault container
|
|
99
|
+
${c.cyan}npx 0nmcp vault open <file>${c.reset} Open/decrypt a .0nv container
|
|
100
|
+
${c.cyan}npx 0nmcp vault inspect <file>${c.reset} Inspect container metadata (no passphrase)
|
|
101
|
+
${c.cyan}npx 0nmcp vault verify <file>${c.reset} Verify Seal of Truth + signature
|
|
102
|
+
${c.cyan}npx 0nmcp vault escrow create${c.reset} Generate escrow keypairs
|
|
103
|
+
${c.cyan}npx 0nmcp vault escrow unwrap${c.reset} Unwrap container with escrow key
|
|
104
|
+
${c.cyan}npx 0nmcp vault transfer <file>${c.reset} Register a vault transfer
|
|
105
|
+
${c.cyan}npx 0nmcp vault revoke <id>${c.reset} Revoke a transfer ID
|
|
106
|
+
|
|
107
|
+
${c.bright}Application commands:${c.reset}
|
|
108
|
+
|
|
109
|
+
${c.cyan}npx 0nmcp app run <file>${c.reset} Start application server
|
|
110
|
+
${c.cyan}npx 0nmcp app build${c.reset} Build application bundle
|
|
111
|
+
${c.cyan}npx 0nmcp app inspect <file>${c.reset} Show application metadata
|
|
112
|
+
${c.cyan}npx 0nmcp app validate <file>${c.reset} Validate application structure
|
|
113
|
+
${c.cyan}npx 0nmcp app list${c.reset} List installed applications
|
|
114
|
+
|
|
115
|
+
${c.bright}Named Runs (Hotkeys):${c.reset}
|
|
116
|
+
|
|
117
|
+
${c.cyan}npx 0nmcp commands${c.reset} List named RUNs from SWITCH file
|
|
118
|
+
${c.cyan}npx 0nmcp <alias>${c.reset} Execute a named RUN
|
|
119
|
+
${c.cyan}npx 0nmcp shell${c.reset} Interactive REPL (type /command)
|
|
120
|
+
|
|
121
|
+
Define commands in ~/.0n/0n-setup.0n:
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
"commands": {
|
|
125
|
+
"launch": { "type": "pipeline", "steps": [{ "action": "verify" }] },
|
|
126
|
+
"score": { "type": "workflow", "workflow": "lead-scoring" }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
90
130
|
${c.bright}Serve options:${c.reset}
|
|
91
131
|
|
|
92
132
|
${c.cyan}npx 0nmcp serve --port 3000 --host 0.0.0.0${c.reset}
|
|
@@ -217,6 +257,20 @@ ${c.bright}Links:${c.reset}
|
|
|
217
257
|
}
|
|
218
258
|
}
|
|
219
259
|
|
|
260
|
+
// Vault Container
|
|
261
|
+
if (command === 'vault') {
|
|
262
|
+
console.log(BANNER);
|
|
263
|
+
await handleVault(args.slice(1));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// App
|
|
268
|
+
if (command === 'app') {
|
|
269
|
+
console.log(BANNER);
|
|
270
|
+
await handleApp(args.slice(1));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
220
274
|
// Engine
|
|
221
275
|
if (command === 'engine') {
|
|
222
276
|
console.log(BANNER);
|
|
@@ -233,9 +287,57 @@ ${c.bright}Links:${c.reset}
|
|
|
233
287
|
return;
|
|
234
288
|
}
|
|
235
289
|
|
|
290
|
+
// Commands — list named RUNs from SWITCH file
|
|
291
|
+
if (command === 'commands' || command === 'aliases') {
|
|
292
|
+
console.log(BANNER);
|
|
293
|
+
const cmds = listCommands();
|
|
294
|
+
if (cmds.length === 0) {
|
|
295
|
+
console.log(`${c.yellow}No commands defined.${c.reset}`);
|
|
296
|
+
console.log(`\nAdd a ${c.cyan}"commands"${c.reset} section to ${c.cyan}~/.0n/0n-setup.0n${c.reset}`);
|
|
297
|
+
console.log(`\nExample:`);
|
|
298
|
+
console.log(` {`);
|
|
299
|
+
console.log(` "commands": {`);
|
|
300
|
+
console.log(` "launch": { "type": "pipeline", "steps": [{ "action": "verify" }] }`);
|
|
301
|
+
console.log(` }`);
|
|
302
|
+
console.log(` }`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
console.log(`${c.bright}Named Runs:${c.reset}\n`);
|
|
306
|
+
for (const cmd of cmds) {
|
|
307
|
+
const typeTag = cmd.type === 'pipeline' ? `${c.blue}pipeline${c.reset}` : `${c.green}workflow${c.reset}`;
|
|
308
|
+
console.log(` ${c.cyan}0nmcp ${cmd.alias}${c.reset} ${cmd.description || cmd.name} [${typeTag}]`);
|
|
309
|
+
}
|
|
310
|
+
console.log(`\n${c.bright}Tip:${c.reset} Run ${c.cyan}0nmcp shell${c.reset} for an interactive REPL where you can type ${c.cyan}/launch${c.reset}, ${c.cyan}/hello${c.reset}, etc.`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Shell — interactive REPL
|
|
315
|
+
if (command === 'shell' || command === 'repl') {
|
|
316
|
+
console.log(BANNER);
|
|
317
|
+
await startShell();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Dynamic command aliases from SWITCH file
|
|
322
|
+
const switchCommands = loadCommands();
|
|
323
|
+
if (switchCommands.has(command)) {
|
|
324
|
+
console.log(BANNER);
|
|
325
|
+
const def = switchCommands.get(command);
|
|
326
|
+
const cliArgs = args.slice(1);
|
|
327
|
+
await runCommand(command, def, cliArgs);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
236
331
|
// Unknown command
|
|
237
332
|
console.log(`${c.red}Unknown command: ${command}${c.reset}`);
|
|
238
|
-
|
|
333
|
+
const switchCmds = listCommands();
|
|
334
|
+
if (switchCmds.length > 0) {
|
|
335
|
+
console.log(`\n${c.bright}Available named RUNs:${c.reset}`);
|
|
336
|
+
for (const cmd of switchCmds) {
|
|
337
|
+
console.log(` ${c.cyan}0nmcp ${cmd.alias}${c.reset} ${cmd.description || cmd.name}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
console.log(`\nRun ${c.cyan}npx 0nmcp help${c.reset} for usage`);
|
|
239
341
|
process.exit(1);
|
|
240
342
|
}
|
|
241
343
|
|
|
@@ -256,6 +358,7 @@ function initDotOn() {
|
|
|
256
358
|
path.join(DOT_ON_DIR, 'snapshots'),
|
|
257
359
|
path.join(DOT_ON_DIR, 'history'),
|
|
258
360
|
path.join(DOT_ON_DIR, 'cache'),
|
|
361
|
+
path.join(DOT_ON_DIR, 'apps'),
|
|
259
362
|
];
|
|
260
363
|
|
|
261
364
|
console.log(`${c.bright}Initializing ~/.0n directory...${c.reset}\n`);
|
|
@@ -437,6 +540,229 @@ async function interactiveConnect() {
|
|
|
437
540
|
console.log(` Saved to: ${filePath}`);
|
|
438
541
|
}
|
|
439
542
|
|
|
543
|
+
// ═══════════════════════════════════════════════════════════
|
|
544
|
+
// Vault Container Commands (Patent Pending #63/990,046)
|
|
545
|
+
// ═══════════════════════════════════════════════════════════
|
|
546
|
+
|
|
547
|
+
function promptInput(prompt) {
|
|
548
|
+
return new Promise(resolve => {
|
|
549
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
550
|
+
rl.question(prompt, answer => { rl.close(); resolve(answer); });
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function handleVault(args) {
|
|
555
|
+
const sub = args[0];
|
|
556
|
+
|
|
557
|
+
if (!sub || sub === 'help') {
|
|
558
|
+
console.log(`
|
|
559
|
+
${c.bright}Vault Container Commands${c.reset} ${c.yellow}(Patent Pending #63/990,046)${c.reset}
|
|
560
|
+
|
|
561
|
+
${c.cyan}vault create${c.reset} Create a .0nv vault container
|
|
562
|
+
--layers <names> Comma-separated layer names (default: all)
|
|
563
|
+
--passphrase <pass> Encryption passphrase
|
|
564
|
+
--output <file> Output file path
|
|
565
|
+
|
|
566
|
+
${c.cyan}vault open <file>${c.reset} Open/decrypt a .0nv container
|
|
567
|
+
--passphrase <pass> Decryption passphrase
|
|
568
|
+
--layer <name> Only decrypt this layer
|
|
569
|
+
|
|
570
|
+
${c.cyan}vault inspect <file>${c.reset} Inspect without decrypting
|
|
571
|
+
|
|
572
|
+
${c.cyan}vault verify <file>${c.reset} Verify Seal of Truth + Ed25519 signature
|
|
573
|
+
|
|
574
|
+
${c.cyan}vault escrow create${c.reset} Generate escrow keypairs
|
|
575
|
+
--parties <n> Number of parties (1-8)
|
|
576
|
+
|
|
577
|
+
${c.cyan}vault transfer <file>${c.reset} Register a transfer
|
|
578
|
+
--to <recipient> Recipient identifier
|
|
579
|
+
|
|
580
|
+
${c.cyan}vault revoke <id>${c.reset} Revoke a transfer ID
|
|
581
|
+
|
|
582
|
+
${c.bright}7 Semantic Layers:${c.reset}
|
|
583
|
+
workflows, credentials, env_vars, mcp_configs,
|
|
584
|
+
site_profiles, ai_brain, audit_trail
|
|
585
|
+
`);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (sub === 'create') {
|
|
590
|
+
const { assembleContainer, saveContainer } = await import('./vault/container.js');
|
|
591
|
+
const { LAYER_NAMES } = await import('./vault/layers.js');
|
|
592
|
+
|
|
593
|
+
const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
|
|
594
|
+
const output = getFlag(args, '--output');
|
|
595
|
+
const layerStr = getFlag(args, '--layers');
|
|
596
|
+
const requestedLayers = layerStr ? layerStr.split(',').map(s => s.trim()) : LAYER_NAMES;
|
|
597
|
+
|
|
598
|
+
// Build layer data from ~/.0n directory
|
|
599
|
+
const layers = {};
|
|
600
|
+
for (const name of requestedLayers) {
|
|
601
|
+
if (!LAYER_NAMES.includes(name)) {
|
|
602
|
+
console.log(`${c.red}Invalid layer: ${name}${c.reset}`);
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
layers[name] = { placeholder: true, created: new Date().toISOString() };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
console.log(`\n${c.bright}Creating vault container...${c.reset}`);
|
|
609
|
+
console.log(` Layers: ${Object.keys(layers).join(', ')}`);
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const result = await assembleContainer({ layers, passphrase });
|
|
613
|
+
const homePath = path.join(os.homedir(), '.0n', 'vault');
|
|
614
|
+
if (!fs.existsSync(homePath)) fs.mkdirSync(homePath, { recursive: true });
|
|
615
|
+
const filePath = output || path.join(homePath, `${result.transferId}.0nv`);
|
|
616
|
+
saveContainer(result.buffer, filePath);
|
|
617
|
+
|
|
618
|
+
console.log(`\n${c.green}✓ Container created${c.reset}`);
|
|
619
|
+
console.log(` Transfer ID: ${c.cyan}${result.transferId}${c.reset}`);
|
|
620
|
+
console.log(` Seal of Truth: ${c.cyan}${result.sealHex}${c.reset}`);
|
|
621
|
+
console.log(` Layers: ${result.layerCount}`);
|
|
622
|
+
console.log(` Size: ${result.buffer.length} bytes`);
|
|
623
|
+
console.log(` File: ${c.yellow}${filePath}${c.reset}`);
|
|
624
|
+
} catch (err) {
|
|
625
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
626
|
+
process.exit(1);
|
|
627
|
+
}
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (sub === 'open') {
|
|
632
|
+
const file = args[1];
|
|
633
|
+
if (!file) { console.log(`${c.red}Usage: vault open <file.0nv>${c.reset}`); process.exit(1); }
|
|
634
|
+
|
|
635
|
+
const { disassembleContainer, loadContainer } = await import('./vault/container.js');
|
|
636
|
+
const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
|
|
637
|
+
const layer = getFlag(args, '--layer');
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
const buffer = loadContainer(file);
|
|
641
|
+
const result = await disassembleContainer(buffer, passphrase, layer ? [layer] : null);
|
|
642
|
+
|
|
643
|
+
console.log(`\n${c.green}✓ Container opened${c.reset}`);
|
|
644
|
+
console.log(` Transfer ID: ${c.cyan}${result.metadata.transferId}${c.reset}`);
|
|
645
|
+
console.log(` Seal valid: ${result.seal.valid ? c.green + '✓' : c.red + '✗'}${c.reset}`);
|
|
646
|
+
console.log(` Sig valid: ${result.signature.valid ? c.green + '✓' : c.red + '✗'}${c.reset}`);
|
|
647
|
+
console.log(` Layers:`);
|
|
648
|
+
for (const [name, data] of Object.entries(result.layers)) {
|
|
649
|
+
const preview = JSON.stringify(data).substring(0, 80);
|
|
650
|
+
console.log(` ${c.cyan}${name}${c.reset}: ${preview}...`);
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (sub === 'inspect') {
|
|
660
|
+
const file = args[1];
|
|
661
|
+
if (!file) { console.log(`${c.red}Usage: vault inspect <file.0nv>${c.reset}`); process.exit(1); }
|
|
662
|
+
|
|
663
|
+
const { inspectContainer, loadContainer } = await import('./vault/container.js');
|
|
664
|
+
try {
|
|
665
|
+
const buffer = loadContainer(file);
|
|
666
|
+
const info = inspectContainer(buffer);
|
|
667
|
+
console.log(`\n${c.bright}Container Inspection${c.reset}`);
|
|
668
|
+
console.log(JSON.stringify(info, null, 2));
|
|
669
|
+
} catch (err) {
|
|
670
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (sub === 'verify') {
|
|
677
|
+
const file = args[1];
|
|
678
|
+
if (!file) { console.log(`${c.red}Usage: vault verify <file.0nv>${c.reset}`); process.exit(1); }
|
|
679
|
+
|
|
680
|
+
const { inspectContainer, loadContainer } = await import('./vault/container.js');
|
|
681
|
+
try {
|
|
682
|
+
const buffer = loadContainer(file);
|
|
683
|
+
const info = inspectContainer(buffer);
|
|
684
|
+
const verified = info.seal.valid && info.signature.valid;
|
|
685
|
+
|
|
686
|
+
console.log(`\n${verified ? c.green + '✓ VERIFIED' : c.red + '✗ FAILED'}${c.reset}`);
|
|
687
|
+
console.log(` Seal of Truth: ${info.seal.valid ? c.green + 'Valid' : c.red + 'Invalid'}${c.reset} (${info.seal.algorithm})`);
|
|
688
|
+
console.log(` Signature: ${info.signature.valid ? c.green + 'Valid' : c.red + 'Invalid'}${c.reset} (${info.signature.algorithm})`);
|
|
689
|
+
console.log(` Transfer ID: ${info.metadata.transferId}`);
|
|
690
|
+
console.log(` Created: ${info.metadata.created}`);
|
|
691
|
+
console.log(` Patent: ${info.patent}`);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (sub === 'escrow') {
|
|
700
|
+
const escrowSub = args[1];
|
|
701
|
+
if (escrowSub === 'create') {
|
|
702
|
+
const { generatePartyKeys } = await import('./vault/escrow.js');
|
|
703
|
+
const count = parseInt(getFlag(args, '--parties') || '3');
|
|
704
|
+
|
|
705
|
+
console.log(`\n${c.bright}Generating ${count} escrow keypairs...${c.reset}`);
|
|
706
|
+
const parties = [];
|
|
707
|
+
for (let i = 0; i < count; i++) {
|
|
708
|
+
const party = generatePartyKeys();
|
|
709
|
+
parties.push(party);
|
|
710
|
+
console.log(`\n ${c.cyan}Party ${i + 1}${c.reset} (${party.partyId}):`);
|
|
711
|
+
console.log(` Public: ${party.publicKey.toString('hex').substring(0, 32)}...`);
|
|
712
|
+
console.log(` Secret: ${party.secretKey.toString('hex').substring(0, 32)}...`);
|
|
713
|
+
}
|
|
714
|
+
console.log(`\n${c.green}✓ ${count} keypairs generated${c.reset}`);
|
|
715
|
+
console.log(` ${c.yellow}Save secret keys securely. Share public keys for container creation.${c.reset}`);
|
|
716
|
+
}
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (sub === 'transfer') {
|
|
721
|
+
const file = args[1];
|
|
722
|
+
if (!file) { console.log(`${c.red}Usage: vault transfer <file.0nv>${c.reset}`); process.exit(1); }
|
|
723
|
+
|
|
724
|
+
const { inspectContainer, loadContainer } = await import('./vault/container.js');
|
|
725
|
+
const { registerTransfer } = await import('./vault/registry.js');
|
|
726
|
+
const recipient = getFlag(args, '--to');
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
const buffer = loadContainer(file);
|
|
730
|
+
const info = inspectContainer(buffer);
|
|
731
|
+
const result = registerTransfer(info.metadata.transferId, info.seal.hash, { recipient });
|
|
732
|
+
|
|
733
|
+
if (result.success) {
|
|
734
|
+
console.log(`\n${c.green}✓ Transfer registered${c.reset}`);
|
|
735
|
+
console.log(` Transfer ID: ${c.cyan}${info.metadata.transferId}${c.reset}`);
|
|
736
|
+
if (recipient) console.log(` Recipient: ${recipient}`);
|
|
737
|
+
} else {
|
|
738
|
+
console.log(`\n${c.red}✗ ${result.error}${c.reset}`);
|
|
739
|
+
}
|
|
740
|
+
} catch (err) {
|
|
741
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (sub === 'revoke') {
|
|
748
|
+
const transferId = args[1];
|
|
749
|
+
if (!transferId) { console.log(`${c.red}Usage: vault revoke <transferId>${c.reset}`); process.exit(1); }
|
|
750
|
+
|
|
751
|
+
const { revokeTransfer } = await import('./vault/registry.js');
|
|
752
|
+
const result = revokeTransfer(transferId);
|
|
753
|
+
|
|
754
|
+
if (result.success) {
|
|
755
|
+
console.log(`\n${c.green}✓ Transfer revoked: ${transferId}${c.reset}`);
|
|
756
|
+
} else {
|
|
757
|
+
console.log(`\n${c.red}✗ ${result.error}${c.reset}`);
|
|
758
|
+
}
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
console.log(`${c.red}Unknown vault command: ${sub}${c.reset}`);
|
|
763
|
+
console.log(`Run ${c.cyan}0nmcp vault help${c.reset} for usage.`);
|
|
764
|
+
}
|
|
765
|
+
|
|
440
766
|
async function handleEngine(args) {
|
|
441
767
|
const sub = args[0];
|
|
442
768
|
|
|
@@ -677,4 +1003,344 @@ async function handleEngine(args) {
|
|
|
677
1003
|
process.exit(1);
|
|
678
1004
|
}
|
|
679
1005
|
|
|
1006
|
+
async function handleApp(args) {
|
|
1007
|
+
const sub = args[0];
|
|
1008
|
+
|
|
1009
|
+
if (!sub || sub === 'help') {
|
|
1010
|
+
console.log(`${c.bright}Application Engine — Build & Run .0n Applications${c.reset}\n`);
|
|
1011
|
+
console.log(` ${c.cyan}run <file>${c.reset} Start application server`);
|
|
1012
|
+
console.log(` ${c.cyan}build${c.reset} Build application bundle`);
|
|
1013
|
+
console.log(` ${c.cyan}inspect <file>${c.reset} Show application metadata`);
|
|
1014
|
+
console.log(` ${c.cyan}validate <file>${c.reset} Validate application structure`);
|
|
1015
|
+
console.log(` ${c.cyan}list${c.reset} List installed applications\n`);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (sub === 'run') {
|
|
1020
|
+
const appFile = args[1];
|
|
1021
|
+
if (!appFile) {
|
|
1022
|
+
console.log(`${c.red}Usage: npx 0nmcp app run <file.0n> [--port 3000] [--passphrase <pw>]${c.reset}`);
|
|
1023
|
+
process.exit(1);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const port = Number(getFlag(args, '--port', 3000));
|
|
1027
|
+
let passphrase = getFlag(args, '--passphrase', null);
|
|
1028
|
+
|
|
1029
|
+
if (!passphrase) {
|
|
1030
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1031
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
1032
|
+
passphrase = await ask('Application passphrase: ');
|
|
1033
|
+
rl.close();
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (!passphrase) {
|
|
1037
|
+
console.log(`${c.red}Passphrase required.${c.reset}`);
|
|
1038
|
+
process.exit(1);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
try {
|
|
1042
|
+
const { openApplication } = await import('./engine/app-builder.js');
|
|
1043
|
+
const { Application } = await import('./engine/application.js');
|
|
1044
|
+
const { ApplicationServer } = await import('./engine/app-server.js');
|
|
1045
|
+
|
|
1046
|
+
const bundle = openApplication(appFile, passphrase);
|
|
1047
|
+
const application = new Application(bundle, { passphrase });
|
|
1048
|
+
const server = new ApplicationServer(application);
|
|
1049
|
+
|
|
1050
|
+
await server.start({ port });
|
|
1051
|
+
|
|
1052
|
+
// Keep alive
|
|
1053
|
+
process.on('SIGINT', async () => {
|
|
1054
|
+
console.log(`\n${c.yellow}Shutting down...${c.reset}`);
|
|
1055
|
+
await server.stop();
|
|
1056
|
+
process.exit(0);
|
|
1057
|
+
});
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
1060
|
+
process.exit(1);
|
|
1061
|
+
}
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (sub === 'build') {
|
|
1066
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1067
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
const appName = await ask('Application name: ') || '0n Application';
|
|
1071
|
+
const passphrase = await ask('Bundle passphrase: ');
|
|
1072
|
+
if (!passphrase) {
|
|
1073
|
+
console.log(`${c.red}Passphrase required.${c.reset}`);
|
|
1074
|
+
rl.close();
|
|
1075
|
+
process.exit(1);
|
|
1076
|
+
}
|
|
1077
|
+
rl.close();
|
|
1078
|
+
|
|
1079
|
+
const { createApplication } = await import('./engine/app-builder.js');
|
|
1080
|
+
|
|
1081
|
+
// Load local connections
|
|
1082
|
+
const connectionsDir = path.join(DOT_ON_DIR, 'connections');
|
|
1083
|
+
const connections = {};
|
|
1084
|
+
if (fs.existsSync(connectionsDir)) {
|
|
1085
|
+
const files = fs.readdirSync(connectionsDir).filter(f => f.endsWith('.0n') || f.endsWith('.0n.json'));
|
|
1086
|
+
for (const file of files) {
|
|
1087
|
+
try {
|
|
1088
|
+
const data = JSON.parse(fs.readFileSync(path.join(connectionsDir, file), 'utf8'));
|
|
1089
|
+
if (data.$0n?.sealed) continue;
|
|
1090
|
+
connections[data.service] = {
|
|
1091
|
+
credentials: data.auth?.credentials || {},
|
|
1092
|
+
name: data.$0n?.name || data.service,
|
|
1093
|
+
authType: data.auth?.type || 'api_key',
|
|
1094
|
+
environment: data.environment || 'production',
|
|
1095
|
+
};
|
|
1096
|
+
} catch { /* skip */ }
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Load local workflows
|
|
1101
|
+
const { loadLocalWorkflows } = await import('./engine/index.js');
|
|
1102
|
+
const workflows = loadLocalWorkflows();
|
|
1103
|
+
|
|
1104
|
+
console.log(`\n${c.bright}Building application...${c.reset}\n`);
|
|
1105
|
+
|
|
1106
|
+
const result = createApplication({
|
|
1107
|
+
name: appName,
|
|
1108
|
+
passphrase,
|
|
1109
|
+
connections,
|
|
1110
|
+
workflows,
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
console.log(`${c.green}${c.bright}Application built!${c.reset}\n`);
|
|
1114
|
+
console.log(` Path: ${result.path}`);
|
|
1115
|
+
console.log(` Connections: ${result.manifest.connection_count}`);
|
|
1116
|
+
console.log(` Workflows: ${result.manifest.workflow_count}`);
|
|
1117
|
+
console.log(`\n${c.bright}Run with:${c.reset} ${c.cyan}npx 0nmcp app run ${result.path}${c.reset}`);
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (sub === 'inspect') {
|
|
1126
|
+
const appFile = args[1];
|
|
1127
|
+
if (!appFile) {
|
|
1128
|
+
console.log(`${c.red}Usage: npx 0nmcp app inspect <file.0n>${c.reset}`);
|
|
1129
|
+
process.exit(1);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
try {
|
|
1133
|
+
const { inspectApplication } = await import('./engine/app-builder.js');
|
|
1134
|
+
const info = inspectApplication(appFile);
|
|
1135
|
+
|
|
1136
|
+
console.log(`${c.bright}Application: ${info.name}${c.reset}\n`);
|
|
1137
|
+
console.log(` Version: ${info.version}`);
|
|
1138
|
+
console.log(` Author: ${info.author || '—'}`);
|
|
1139
|
+
console.log(` Created: ${info.created}`);
|
|
1140
|
+
if (info.description) console.log(` Description: ${info.description}`);
|
|
1141
|
+
console.log(`\n Connections: ${info.connections.map(c2 => c2.service).join(', ') || 'none'}`);
|
|
1142
|
+
console.log(` Workflows: ${info.workflows.join(', ') || 'none'}`);
|
|
1143
|
+
console.log(` Operations: ${info.operations.join(', ') || 'none'}`);
|
|
1144
|
+
console.log(` Endpoints: ${info.endpoints.join(', ') || 'none'}`);
|
|
1145
|
+
console.log(` Automations: ${info.automations.join(', ') || 'none'}`);
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (sub === 'validate') {
|
|
1154
|
+
const appFile = args[1];
|
|
1155
|
+
if (!appFile) {
|
|
1156
|
+
console.log(`${c.red}Usage: npx 0nmcp app validate <file.0n>${c.reset}`);
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
try {
|
|
1161
|
+
const { validateApplication } = await import('./engine/app-builder.js');
|
|
1162
|
+
const data = JSON.parse(fs.readFileSync(appFile, 'utf8'));
|
|
1163
|
+
const result = validateApplication(data);
|
|
1164
|
+
|
|
1165
|
+
if (result.valid) {
|
|
1166
|
+
console.log(`${c.green}${c.bright}Application is valid!${c.reset}`);
|
|
1167
|
+
} else {
|
|
1168
|
+
console.log(`${c.red}${c.bright}Validation failed:${c.reset}\n`);
|
|
1169
|
+
for (const err of result.errors) {
|
|
1170
|
+
console.log(` ${c.red}●${c.reset} ${err}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (result.warnings?.length > 0) {
|
|
1175
|
+
console.log(`\n${c.yellow}Warnings:${c.reset}`);
|
|
1176
|
+
for (const w of result.warnings) {
|
|
1177
|
+
console.log(` ${c.yellow}○${c.reset} ${w}`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
1182
|
+
process.exit(1);
|
|
1183
|
+
}
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if (sub === 'list') {
|
|
1188
|
+
const appsDir = path.join(DOT_ON_DIR, 'apps');
|
|
1189
|
+
if (!fs.existsSync(appsDir)) {
|
|
1190
|
+
console.log(`${c.yellow}No applications installed.${c.reset}`);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const files = fs.readdirSync(appsDir).filter(f => f.endsWith('.0n') || f.endsWith('.0n.json'));
|
|
1195
|
+
if (files.length === 0) {
|
|
1196
|
+
console.log(`${c.yellow}No applications installed.${c.reset}`);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
console.log(`${c.bright}Installed Applications:${c.reset}\n`);
|
|
1201
|
+
|
|
1202
|
+
for (const file of files) {
|
|
1203
|
+
try {
|
|
1204
|
+
const data = JSON.parse(fs.readFileSync(path.join(appsDir, file), 'utf8'));
|
|
1205
|
+
if (data.$0n?.type !== 'application') continue;
|
|
1206
|
+
|
|
1207
|
+
const wfCount = Object.keys(data.workflows || {}).length;
|
|
1208
|
+
const epCount = Object.keys(data.endpoints || {}).length;
|
|
1209
|
+
console.log(` ${c.green}●${c.reset} ${c.bright}${data.$0n.name || file}${c.reset} v${data.$0n.version || '1.0.0'}`);
|
|
1210
|
+
console.log(` ${wfCount} workflows, ${epCount} endpoints`);
|
|
1211
|
+
console.log(` ${file}`);
|
|
1212
|
+
console.log('');
|
|
1213
|
+
} catch {
|
|
1214
|
+
console.log(` ${c.red}●${c.reset} ${file} (error reading)`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
console.log(`${c.red}Unknown app command: ${sub}${c.reset}`);
|
|
1221
|
+
console.log(`Run ${c.cyan}npx 0nmcp app help${c.reset} for usage`);
|
|
1222
|
+
process.exit(1);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async function startShell() {
|
|
1226
|
+
const cmds = loadCommands();
|
|
1227
|
+
const aliases = [...cmds.keys()];
|
|
1228
|
+
const builtins = ['help', 'commands', 'list', 'exit', 'quit'];
|
|
1229
|
+
const allCompletions = [...builtins, ...aliases];
|
|
1230
|
+
|
|
1231
|
+
console.log(`${c.bright}0nMCP Interactive Shell${c.reset}`);
|
|
1232
|
+
console.log(`Type ${c.cyan}/command${c.reset} to run a named RUN, ${c.cyan}/commands${c.reset} to list, ${c.cyan}/exit${c.reset} to quit.\n`);
|
|
1233
|
+
|
|
1234
|
+
if (aliases.length > 0) {
|
|
1235
|
+
console.log(`${c.bright}Available commands:${c.reset} ${aliases.map(a => c.cyan + '/' + a + c.reset).join(', ')}\n`);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const rl = readline.createInterface({
|
|
1239
|
+
input: process.stdin,
|
|
1240
|
+
output: process.stdout,
|
|
1241
|
+
prompt: `${c.green}0n>${c.reset} `,
|
|
1242
|
+
completer: (line) => {
|
|
1243
|
+
const input = line.startsWith('/') ? line.slice(1) : line;
|
|
1244
|
+
const hits = allCompletions.filter(c2 => c2.startsWith(input));
|
|
1245
|
+
return [hits.map(h => '/' + h), line];
|
|
1246
|
+
},
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
rl.prompt();
|
|
1250
|
+
|
|
1251
|
+
rl.on('line', async (line) => {
|
|
1252
|
+
const input = line.trim();
|
|
1253
|
+
if (!input) {
|
|
1254
|
+
rl.prompt();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Strip leading / if present
|
|
1259
|
+
const raw = input.startsWith('/') ? input.slice(1) : input;
|
|
1260
|
+
const parts = raw.split(/\s+/);
|
|
1261
|
+
const cmd = parts[0];
|
|
1262
|
+
const cmdArgs = parts.slice(1);
|
|
1263
|
+
|
|
1264
|
+
if (cmd === 'exit' || cmd === 'quit') {
|
|
1265
|
+
console.log(`\n${c.cyan}Goodbye!${c.reset}`);
|
|
1266
|
+
rl.close();
|
|
1267
|
+
process.exit(0);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (cmd === 'help') {
|
|
1271
|
+
console.log(`\n${c.bright}Shell Commands:${c.reset}`);
|
|
1272
|
+
console.log(` ${c.cyan}/commands${c.reset} List all named RUNs`);
|
|
1273
|
+
console.log(` ${c.cyan}/list${c.reset} Show connected services`);
|
|
1274
|
+
console.log(` ${c.cyan}/exit${c.reset} Exit the shell`);
|
|
1275
|
+
if (aliases.length > 0) {
|
|
1276
|
+
console.log(`\n${c.bright}Named RUNs:${c.reset}`);
|
|
1277
|
+
for (const a of aliases) {
|
|
1278
|
+
const def = cmds.get(a);
|
|
1279
|
+
console.log(` ${c.cyan}/${a}${c.reset} ${def.description || def.name || ''}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
console.log('');
|
|
1283
|
+
rl.prompt();
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (cmd === 'commands') {
|
|
1288
|
+
if (aliases.length === 0) {
|
|
1289
|
+
console.log(`\n${c.yellow}No commands defined in SWITCH file.${c.reset}\n`);
|
|
1290
|
+
} else {
|
|
1291
|
+
console.log(`\n${c.bright}Named RUNs:${c.reset}`);
|
|
1292
|
+
for (const a of aliases) {
|
|
1293
|
+
const def = cmds.get(a);
|
|
1294
|
+
console.log(` ${c.cyan}/${a}${c.reset} ${def.description || def.name || ''}`);
|
|
1295
|
+
}
|
|
1296
|
+
console.log('');
|
|
1297
|
+
}
|
|
1298
|
+
rl.prompt();
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (cmd === 'list') {
|
|
1303
|
+
const connectionsDir = path.join(DOT_ON_DIR, 'connections');
|
|
1304
|
+
if (fs.existsSync(connectionsDir)) {
|
|
1305
|
+
const files = fs.readdirSync(connectionsDir).filter(f => f.endsWith('.0n'));
|
|
1306
|
+
console.log(`\n${c.bright}Connected Services:${c.reset}`);
|
|
1307
|
+
for (const file of files) {
|
|
1308
|
+
try {
|
|
1309
|
+
const data = JSON.parse(fs.readFileSync(path.join(connectionsDir, file), 'utf8'));
|
|
1310
|
+
console.log(` ${c.green}●${c.reset} ${data.$0n?.name || data.service}`);
|
|
1311
|
+
} catch {
|
|
1312
|
+
console.log(` ${c.red}●${c.reset} ${file} (error)`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
console.log('');
|
|
1316
|
+
} else {
|
|
1317
|
+
console.log(`\n${c.yellow}No connections found.${c.reset}\n`);
|
|
1318
|
+
}
|
|
1319
|
+
rl.prompt();
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Try to run as a named command
|
|
1324
|
+
if (cmds.has(cmd)) {
|
|
1325
|
+
console.log('');
|
|
1326
|
+
try {
|
|
1327
|
+
await runCommand(cmd, cmds.get(cmd), cmdArgs);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
console.log(`${c.red}Error: ${err.message}${c.reset}`);
|
|
1330
|
+
}
|
|
1331
|
+
console.log('');
|
|
1332
|
+
rl.prompt();
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
console.log(`${c.yellow}Unknown command: ${cmd}${c.reset}`);
|
|
1337
|
+
console.log(`Type ${c.cyan}/help${c.reset} for available commands.`);
|
|
1338
|
+
rl.prompt();
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
rl.on('close', () => {
|
|
1342
|
+
process.exit(0);
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
680
1346
|
main().catch(console.error);
|