0nmcp 1.7.0 → 2.1.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
@@ -11,6 +11,9 @@
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)
16
19
  * npx 0nmcp app Application commands (run, build, inspect, validate, list)
@@ -24,6 +27,8 @@ import { fileURLToPath } from 'url';
24
27
  import fs from 'fs';
25
28
  import os from 'os';
26
29
  import readline from 'readline';
30
+ import { loadCommands, listCommands } from './commands.js';
31
+ import { runCommand } from './command-runner.js';
27
32
 
28
33
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
34
 
@@ -88,6 +93,17 @@ ${c.bright}Engine commands:${c.reset}
88
93
  ${c.cyan}npx 0nmcp engine export${c.reset} Export .0n bundle (AI Brain)
89
94
  ${c.cyan}npx 0nmcp engine open <bundle>${c.reset} Open/inspect a .0n bundle
90
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
+
91
107
  ${c.bright}Application commands:${c.reset}
92
108
 
93
109
  ${c.cyan}npx 0nmcp app run <file>${c.reset} Start application server
@@ -96,6 +112,21 @@ ${c.bright}Application commands:${c.reset}
96
112
  ${c.cyan}npx 0nmcp app validate <file>${c.reset} Validate application structure
97
113
  ${c.cyan}npx 0nmcp app list${c.reset} List installed applications
98
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
+
99
130
  ${c.bright}Serve options:${c.reset}
100
131
 
101
132
  ${c.cyan}npx 0nmcp serve --port 3000 --host 0.0.0.0${c.reset}
@@ -226,6 +257,13 @@ ${c.bright}Links:${c.reset}
226
257
  }
227
258
  }
228
259
 
260
+ // Vault Container
261
+ if (command === 'vault') {
262
+ console.log(BANNER);
263
+ await handleVault(args.slice(1));
264
+ return;
265
+ }
266
+
229
267
  // App
230
268
  if (command === 'app') {
231
269
  console.log(BANNER);
@@ -233,6 +271,13 @@ ${c.bright}Links:${c.reset}
233
271
  return;
234
272
  }
235
273
 
274
+ // Business Deed
275
+ if (command === 'deed') {
276
+ console.log(BANNER);
277
+ await handleDeed(args.slice(1));
278
+ return;
279
+ }
280
+
236
281
  // Engine
237
282
  if (command === 'engine') {
238
283
  console.log(BANNER);
@@ -249,9 +294,57 @@ ${c.bright}Links:${c.reset}
249
294
  return;
250
295
  }
251
296
 
297
+ // Commands — list named RUNs from SWITCH file
298
+ if (command === 'commands' || command === 'aliases') {
299
+ console.log(BANNER);
300
+ const cmds = listCommands();
301
+ if (cmds.length === 0) {
302
+ console.log(`${c.yellow}No commands defined.${c.reset}`);
303
+ console.log(`\nAdd a ${c.cyan}"commands"${c.reset} section to ${c.cyan}~/.0n/0n-setup.0n${c.reset}`);
304
+ console.log(`\nExample:`);
305
+ console.log(` {`);
306
+ console.log(` "commands": {`);
307
+ console.log(` "launch": { "type": "pipeline", "steps": [{ "action": "verify" }] }`);
308
+ console.log(` }`);
309
+ console.log(` }`);
310
+ return;
311
+ }
312
+ console.log(`${c.bright}Named Runs:${c.reset}\n`);
313
+ for (const cmd of cmds) {
314
+ const typeTag = cmd.type === 'pipeline' ? `${c.blue}pipeline${c.reset}` : `${c.green}workflow${c.reset}`;
315
+ console.log(` ${c.cyan}0nmcp ${cmd.alias}${c.reset} ${cmd.description || cmd.name} [${typeTag}]`);
316
+ }
317
+ 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.`);
318
+ return;
319
+ }
320
+
321
+ // Shell — interactive REPL
322
+ if (command === 'shell' || command === 'repl') {
323
+ console.log(BANNER);
324
+ await startShell();
325
+ return;
326
+ }
327
+
328
+ // Dynamic command aliases from SWITCH file
329
+ const switchCommands = loadCommands();
330
+ if (switchCommands.has(command)) {
331
+ console.log(BANNER);
332
+ const def = switchCommands.get(command);
333
+ const cliArgs = args.slice(1);
334
+ await runCommand(command, def, cliArgs);
335
+ return;
336
+ }
337
+
252
338
  // Unknown command
253
339
  console.log(`${c.red}Unknown command: ${command}${c.reset}`);
254
- console.log(`Run ${c.cyan}npx 0nmcp help${c.reset} for usage`);
340
+ const switchCmds = listCommands();
341
+ if (switchCmds.length > 0) {
342
+ console.log(`\n${c.bright}Available named RUNs:${c.reset}`);
343
+ for (const cmd of switchCmds) {
344
+ console.log(` ${c.cyan}0nmcp ${cmd.alias}${c.reset} ${cmd.description || cmd.name}`);
345
+ }
346
+ }
347
+ console.log(`\nRun ${c.cyan}npx 0nmcp help${c.reset} for usage`);
255
348
  process.exit(1);
256
349
  }
257
350
 
@@ -454,6 +547,496 @@ async function interactiveConnect() {
454
547
  console.log(` Saved to: ${filePath}`);
455
548
  }
456
549
 
550
+ // ═══════════════════════════════════════════════════════════
551
+ // Vault Container Commands (Patent Pending #63/990,046)
552
+ // ═══════════════════════════════════════════════════════════
553
+
554
+ function promptInput(prompt) {
555
+ return new Promise(resolve => {
556
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
557
+ rl.question(prompt, answer => { rl.close(); resolve(answer); });
558
+ });
559
+ }
560
+
561
+ async function handleVault(args) {
562
+ const sub = args[0];
563
+
564
+ if (!sub || sub === 'help') {
565
+ console.log(`
566
+ ${c.bright}Vault Container Commands${c.reset} ${c.yellow}(Patent Pending #63/990,046)${c.reset}
567
+
568
+ ${c.cyan}vault create${c.reset} Create a .0nv vault container
569
+ --layers <names> Comma-separated layer names (default: all)
570
+ --passphrase <pass> Encryption passphrase
571
+ --output <file> Output file path
572
+
573
+ ${c.cyan}vault open <file>${c.reset} Open/decrypt a .0nv container
574
+ --passphrase <pass> Decryption passphrase
575
+ --layer <name> Only decrypt this layer
576
+
577
+ ${c.cyan}vault inspect <file>${c.reset} Inspect without decrypting
578
+
579
+ ${c.cyan}vault verify <file>${c.reset} Verify Seal of Truth + Ed25519 signature
580
+
581
+ ${c.cyan}vault escrow create${c.reset} Generate escrow keypairs
582
+ --parties <n> Number of parties (1-8)
583
+
584
+ ${c.cyan}vault transfer <file>${c.reset} Register a transfer
585
+ --to <recipient> Recipient identifier
586
+
587
+ ${c.cyan}vault revoke <id>${c.reset} Revoke a transfer ID
588
+
589
+ ${c.bright}7 Semantic Layers:${c.reset}
590
+ workflows, credentials, env_vars, mcp_configs,
591
+ site_profiles, ai_brain, audit_trail
592
+ `);
593
+ return;
594
+ }
595
+
596
+ if (sub === 'create') {
597
+ const { assembleContainer, saveContainer } = await import('./vault/container.js');
598
+ const { LAYER_NAMES } = await import('./vault/layers.js');
599
+
600
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
601
+ const output = getFlag(args, '--output');
602
+ const layerStr = getFlag(args, '--layers');
603
+ const requestedLayers = layerStr ? layerStr.split(',').map(s => s.trim()) : LAYER_NAMES;
604
+
605
+ // Build layer data from ~/.0n directory
606
+ const layers = {};
607
+ for (const name of requestedLayers) {
608
+ if (!LAYER_NAMES.includes(name)) {
609
+ console.log(`${c.red}Invalid layer: ${name}${c.reset}`);
610
+ process.exit(1);
611
+ }
612
+ layers[name] = { placeholder: true, created: new Date().toISOString() };
613
+ }
614
+
615
+ console.log(`\n${c.bright}Creating vault container...${c.reset}`);
616
+ console.log(` Layers: ${Object.keys(layers).join(', ')}`);
617
+
618
+ try {
619
+ const result = await assembleContainer({ layers, passphrase });
620
+ const homePath = path.join(os.homedir(), '.0n', 'vault');
621
+ if (!fs.existsSync(homePath)) fs.mkdirSync(homePath, { recursive: true });
622
+ const filePath = output || path.join(homePath, `${result.transferId}.0nv`);
623
+ saveContainer(result.buffer, filePath);
624
+
625
+ console.log(`\n${c.green}✓ Container created${c.reset}`);
626
+ console.log(` Transfer ID: ${c.cyan}${result.transferId}${c.reset}`);
627
+ console.log(` Seal of Truth: ${c.cyan}${result.sealHex}${c.reset}`);
628
+ console.log(` Layers: ${result.layerCount}`);
629
+ console.log(` Size: ${result.buffer.length} bytes`);
630
+ console.log(` File: ${c.yellow}${filePath}${c.reset}`);
631
+ } catch (err) {
632
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
633
+ process.exit(1);
634
+ }
635
+ return;
636
+ }
637
+
638
+ if (sub === 'open') {
639
+ const file = args[1];
640
+ if (!file) { console.log(`${c.red}Usage: vault open <file.0nv>${c.reset}`); process.exit(1); }
641
+
642
+ const { disassembleContainer, loadContainer } = await import('./vault/container.js');
643
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
644
+ const layer = getFlag(args, '--layer');
645
+
646
+ try {
647
+ const buffer = loadContainer(file);
648
+ const result = await disassembleContainer(buffer, passphrase, layer ? [layer] : null);
649
+
650
+ console.log(`\n${c.green}✓ Container opened${c.reset}`);
651
+ console.log(` Transfer ID: ${c.cyan}${result.metadata.transferId}${c.reset}`);
652
+ console.log(` Seal valid: ${result.seal.valid ? c.green + '✓' : c.red + '✗'}${c.reset}`);
653
+ console.log(` Sig valid: ${result.signature.valid ? c.green + '✓' : c.red + '✗'}${c.reset}`);
654
+ console.log(` Layers:`);
655
+ for (const [name, data] of Object.entries(result.layers)) {
656
+ const preview = JSON.stringify(data).substring(0, 80);
657
+ console.log(` ${c.cyan}${name}${c.reset}: ${preview}...`);
658
+ }
659
+ } catch (err) {
660
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
661
+ process.exit(1);
662
+ }
663
+ return;
664
+ }
665
+
666
+ if (sub === 'inspect') {
667
+ const file = args[1];
668
+ if (!file) { console.log(`${c.red}Usage: vault inspect <file.0nv>${c.reset}`); process.exit(1); }
669
+
670
+ const { inspectContainer, loadContainer } = await import('./vault/container.js');
671
+ try {
672
+ const buffer = loadContainer(file);
673
+ const info = inspectContainer(buffer);
674
+ console.log(`\n${c.bright}Container Inspection${c.reset}`);
675
+ console.log(JSON.stringify(info, null, 2));
676
+ } catch (err) {
677
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
678
+ process.exit(1);
679
+ }
680
+ return;
681
+ }
682
+
683
+ if (sub === 'verify') {
684
+ const file = args[1];
685
+ if (!file) { console.log(`${c.red}Usage: vault verify <file.0nv>${c.reset}`); process.exit(1); }
686
+
687
+ const { inspectContainer, loadContainer } = await import('./vault/container.js');
688
+ try {
689
+ const buffer = loadContainer(file);
690
+ const info = inspectContainer(buffer);
691
+ const verified = info.seal.valid && info.signature.valid;
692
+
693
+ console.log(`\n${verified ? c.green + '✓ VERIFIED' : c.red + '✗ FAILED'}${c.reset}`);
694
+ console.log(` Seal of Truth: ${info.seal.valid ? c.green + 'Valid' : c.red + 'Invalid'}${c.reset} (${info.seal.algorithm})`);
695
+ console.log(` Signature: ${info.signature.valid ? c.green + 'Valid' : c.red + 'Invalid'}${c.reset} (${info.signature.algorithm})`);
696
+ console.log(` Transfer ID: ${info.metadata.transferId}`);
697
+ console.log(` Created: ${info.metadata.created}`);
698
+ console.log(` Patent: ${info.patent}`);
699
+ } catch (err) {
700
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
701
+ process.exit(1);
702
+ }
703
+ return;
704
+ }
705
+
706
+ if (sub === 'escrow') {
707
+ const escrowSub = args[1];
708
+ if (escrowSub === 'create') {
709
+ const { generatePartyKeys } = await import('./vault/escrow.js');
710
+ const count = parseInt(getFlag(args, '--parties') || '3');
711
+
712
+ console.log(`\n${c.bright}Generating ${count} escrow keypairs...${c.reset}`);
713
+ const parties = [];
714
+ for (let i = 0; i < count; i++) {
715
+ const party = generatePartyKeys();
716
+ parties.push(party);
717
+ console.log(`\n ${c.cyan}Party ${i + 1}${c.reset} (${party.partyId}):`);
718
+ console.log(` Public: ${party.publicKey.toString('hex').substring(0, 32)}...`);
719
+ console.log(` Secret: ${party.secretKey.toString('hex').substring(0, 32)}...`);
720
+ }
721
+ console.log(`\n${c.green}✓ ${count} keypairs generated${c.reset}`);
722
+ console.log(` ${c.yellow}Save secret keys securely. Share public keys for container creation.${c.reset}`);
723
+ }
724
+ return;
725
+ }
726
+
727
+ if (sub === 'transfer') {
728
+ const file = args[1];
729
+ if (!file) { console.log(`${c.red}Usage: vault transfer <file.0nv>${c.reset}`); process.exit(1); }
730
+
731
+ const { inspectContainer, loadContainer } = await import('./vault/container.js');
732
+ const { registerTransfer } = await import('./vault/registry.js');
733
+ const recipient = getFlag(args, '--to');
734
+
735
+ try {
736
+ const buffer = loadContainer(file);
737
+ const info = inspectContainer(buffer);
738
+ const result = registerTransfer(info.metadata.transferId, info.seal.hash, { recipient });
739
+
740
+ if (result.success) {
741
+ console.log(`\n${c.green}✓ Transfer registered${c.reset}`);
742
+ console.log(` Transfer ID: ${c.cyan}${info.metadata.transferId}${c.reset}`);
743
+ if (recipient) console.log(` Recipient: ${recipient}`);
744
+ } else {
745
+ console.log(`\n${c.red}✗ ${result.error}${c.reset}`);
746
+ }
747
+ } catch (err) {
748
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
749
+ process.exit(1);
750
+ }
751
+ return;
752
+ }
753
+
754
+ if (sub === 'revoke') {
755
+ const transferId = args[1];
756
+ if (!transferId) { console.log(`${c.red}Usage: vault revoke <transferId>${c.reset}`); process.exit(1); }
757
+
758
+ const { revokeTransfer } = await import('./vault/registry.js');
759
+ const result = revokeTransfer(transferId);
760
+
761
+ if (result.success) {
762
+ console.log(`\n${c.green}✓ Transfer revoked: ${transferId}${c.reset}`);
763
+ } else {
764
+ console.log(`\n${c.red}✗ ${result.error}${c.reset}`);
765
+ }
766
+ return;
767
+ }
768
+
769
+ console.log(`${c.red}Unknown vault command: ${sub}${c.reset}`);
770
+ console.log(`Run ${c.cyan}0nmcp vault help${c.reset} for usage.`);
771
+ }
772
+
773
+ async function handleDeed(args) {
774
+ const sub = args[0];
775
+
776
+ if (!sub || sub === 'help') {
777
+ console.log(`
778
+ ${c.bright}Business Deed — Digital Asset Transfer System${c.reset} ${c.yellow}(Patent Pending #63/990,046)${c.reset}
779
+
780
+ ${c.cyan}deed create${c.reset} Create a Business Deed (.0nv)
781
+ --name <name> Business name (required)
782
+ --from <file> Import credentials from .env, .json, or .csv
783
+ --passphrase <pass> Encryption passphrase
784
+ --output <file> Output file path
785
+
786
+ ${c.cyan}deed open <file>${c.reset} Open/decrypt a Business Deed
787
+ --passphrase <pass> Decryption passphrase
788
+ --layer <name> Only decrypt this layer
789
+
790
+ ${c.cyan}deed inspect <file>${c.reset} Inspect deed metadata (no passphrase needed)
791
+
792
+ ${c.cyan}deed verify <file>${c.reset} Verify Seal of Truth + Ed25519 signature
793
+
794
+ ${c.cyan}deed accept <file>${c.reset} Accept a deed transfer (buyer)
795
+ --passphrase <pass> Current deed passphrase
796
+ --buyer-name <name> Buyer's name
797
+ --buyer-email <email> Buyer's email
798
+ --new-passphrase <pass> New passphrase for re-encrypted deed
799
+
800
+ ${c.cyan}deed import <file>${c.reset} Import deed to live system config
801
+ --passphrase <pass> Decryption passphrase
802
+ --target <dir> Target directory (default: ~/.0n/)
803
+
804
+ ${c.cyan}deed export${c.reset} Collect + package in one step
805
+ --from <file> Source file (.env, .json, .csv)
806
+ --name <name> Business name
807
+ --passphrase <pass> Encryption passphrase
808
+ --output <file> Output file path
809
+
810
+ ${c.bright}Lifecycle:${c.reset} CREATE > PACKAGE > ESCROW > ACCEPT > IMPORT > FLIP
811
+ `);
812
+ return;
813
+ }
814
+
815
+ if (sub === 'create') {
816
+ const { BusinessDeed } = await import('./vault/deed.js');
817
+ const deed = new BusinessDeed();
818
+
819
+ const name = getFlag(args, '--name') || await promptInput('Business name: ');
820
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
821
+ const output = getFlag(args, '--output');
822
+ const from = getFlag(args, '--from');
823
+
824
+ let createOpts = { name, passphrase, output };
825
+
826
+ if (from) {
827
+ // Collect from file
828
+ const { collectCredentials } = await import('./vault/deed-collector.js');
829
+ console.log(`\n${c.bright}Collecting credentials from ${from}...${c.reset}`);
830
+ const collected = await collectCredentials({ envFile: from, jsonFile: from, csvFile: from });
831
+ createOpts.credentials = collected.credentials;
832
+ createOpts.envVars = collected.envVars;
833
+ console.log(` Found ${collected.credentialCount} credentials across ${collected.services.length} services`);
834
+ if (collected.services.length > 0) {
835
+ console.log(` Services: ${collected.services.join(', ')}`);
836
+ }
837
+ }
838
+
839
+ console.log(`\n${c.bright}Creating Business Deed...${c.reset}`);
840
+ try {
841
+ const result = await deed.create(createOpts);
842
+ console.log(`\n${c.green}✓ Business Deed created${c.reset}`);
843
+ console.log(` Business: ${c.cyan}${name}${c.reset}`);
844
+ console.log(` Transfer ID: ${c.cyan}${result.transferId}${c.reset}`);
845
+ console.log(` Seal of Truth: ${c.cyan}${result.sealHex}${c.reset}`);
846
+ console.log(` Services: ${result.services.join(', ') || 'none'}`);
847
+ console.log(` Credentials: ${result.credentialCount}`);
848
+ console.log(` Layers: ${result.layerCount}`);
849
+ console.log(` Size: ${result.containerSize} bytes`);
850
+ console.log(` File: ${c.yellow}${result.file}${c.reset}`);
851
+ } catch (err) {
852
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
853
+ process.exit(1);
854
+ }
855
+ return;
856
+ }
857
+
858
+ if (sub === 'open') {
859
+ const file = args[1];
860
+ if (!file) { console.log(`${c.red}Usage: deed open <file.0nv>${c.reset}`); process.exit(1); }
861
+
862
+ const { BusinessDeed } = await import('./vault/deed.js');
863
+ const deed = new BusinessDeed();
864
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
865
+ const layer = getFlag(args, '--layer');
866
+
867
+ try {
868
+ const result = await deed.open(file, passphrase, layer ? [layer] : null);
869
+
870
+ console.log(`\n${c.green}✓ Business Deed opened${c.reset}`);
871
+ console.log(` Transfer ID: ${c.cyan}${result.metadata.transferId}${c.reset}`);
872
+ console.log(` Seal valid: ${result.seal.valid ? c.green + '✓' : c.red + '✗'}${c.reset}`);
873
+ console.log(` Sig valid: ${result.signature.valid ? c.green + '✓' : c.red + '✗'}${c.reset}`);
874
+
875
+ if (result.deed) {
876
+ console.log(` Business: ${c.cyan}${result.deed.business?.name || 'Unknown'}${c.reset}`);
877
+ console.log(` Services: ${(result.deed.services || []).join(', ')}`);
878
+ console.log(` Transfers: ${(result.deed.transfer_history || []).length}`);
879
+ }
880
+
881
+ console.log(`\n${c.bright}Layers:${c.reset}`);
882
+ for (const [name, data] of Object.entries(result.layers)) {
883
+ if (name === 'audit_trail') continue;
884
+ const preview = JSON.stringify(data).substring(0, 100);
885
+ console.log(` ${c.cyan}${name}${c.reset}: ${preview}${preview.length >= 100 ? '...' : ''}`);
886
+ }
887
+ } catch (err) {
888
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
889
+ process.exit(1);
890
+ }
891
+ return;
892
+ }
893
+
894
+ if (sub === 'inspect') {
895
+ const file = args[1];
896
+ if (!file) { console.log(`${c.red}Usage: deed inspect <file.0nv>${c.reset}`); process.exit(1); }
897
+
898
+ const { BusinessDeed } = await import('./vault/deed.js');
899
+ const deed = new BusinessDeed();
900
+
901
+ try {
902
+ const info = deed.inspect(file);
903
+ console.log(`\n${c.bright}Business Deed Inspection${c.reset}`);
904
+ console.log(JSON.stringify(info, null, 2));
905
+ } catch (err) {
906
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
907
+ process.exit(1);
908
+ }
909
+ return;
910
+ }
911
+
912
+ if (sub === 'verify') {
913
+ const file = args[1];
914
+ if (!file) { console.log(`${c.red}Usage: deed verify <file.0nv>${c.reset}`); process.exit(1); }
915
+
916
+ const { BusinessDeed } = await import('./vault/deed.js');
917
+ const deed = new BusinessDeed();
918
+
919
+ try {
920
+ const v = deed.verify(file);
921
+ console.log(`\n${v.verified ? c.green + '✓ VERIFIED' : c.red + '✗ FAILED'}${c.reset}`);
922
+ console.log(` Seal of Truth: ${v.seal.valid ? c.green + 'Valid' : c.red + 'Invalid'}${c.reset} (${v.seal.algorithm})`);
923
+ console.log(` Signature: ${v.signature.valid ? c.green + 'Valid' : c.red + 'Invalid'}${c.reset} (${v.signature.algorithm})`);
924
+ console.log(` Transfer ID: ${v.transferId}`);
925
+ console.log(` Created: ${v.created}`);
926
+ console.log(` Patent: ${v.patent}`);
927
+ } catch (err) {
928
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
929
+ process.exit(1);
930
+ }
931
+ return;
932
+ }
933
+
934
+ if (sub === 'accept') {
935
+ const file = args[1];
936
+ if (!file) { console.log(`${c.red}Usage: deed accept <file.0nv>${c.reset}`); process.exit(1); }
937
+
938
+ const { BusinessDeed } = await import('./vault/deed.js');
939
+ const deed = new BusinessDeed();
940
+
941
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Current passphrase: ');
942
+ const buyerName = getFlag(args, '--buyer-name') || await promptInput('Buyer name: ');
943
+ const buyerEmail = getFlag(args, '--buyer-email') || await promptInput('Buyer email: ');
944
+ const newPassphrase = getFlag(args, '--new-passphrase');
945
+
946
+ try {
947
+ const result = await deed.accept(
948
+ file, passphrase,
949
+ { name: buyerName, email: buyerEmail },
950
+ newPassphrase,
951
+ );
952
+
953
+ console.log(`\n${c.green}✓ Business Deed accepted${c.reset}`);
954
+ console.log(` New Transfer ID: ${c.cyan}${result.transferId}${c.reset}`);
955
+ console.log(` New Seal: ${c.cyan}${result.sealHex}${c.reset}`);
956
+ console.log(` Buyer: ${buyerName} <${buyerEmail}>`);
957
+ console.log(` Prev Transfer: ${result.previousTransferId}`);
958
+ console.log(` File: ${c.yellow}${result.file}${c.reset}`);
959
+ } catch (err) {
960
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
961
+ process.exit(1);
962
+ }
963
+ return;
964
+ }
965
+
966
+ if (sub === 'import') {
967
+ const file = args[1];
968
+ if (!file) { console.log(`${c.red}Usage: deed import <file.0nv>${c.reset}`); process.exit(1); }
969
+
970
+ const { BusinessDeed } = await import('./vault/deed.js');
971
+ const deed = new BusinessDeed();
972
+
973
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
974
+ const target = getFlag(args, '--target');
975
+
976
+ try {
977
+ const report = await deed.importDeed(file, passphrase, target);
978
+
979
+ console.log(`\n${report.success ? c.green + '✓ Import complete' : c.red + '✗ Import had errors'}${c.reset}`);
980
+ if (report.connections.written.length > 0) {
981
+ console.log(` Connections: ${report.connections.written.join(', ')}`);
982
+ }
983
+ if (report.envFile) {
984
+ console.log(` Env file: ${report.envFile.file} (${report.envFile.written} vars)`);
985
+ }
986
+ if (report.workflows.count > 0) {
987
+ console.log(` Workflows: ${report.workflows.count}`);
988
+ }
989
+ if (report.brain.written.length > 0) {
990
+ console.log(` AI Brain: ${report.brain.written.join(', ')}`);
991
+ }
992
+ if (report.deed) {
993
+ console.log(` Business: ${c.cyan}${report.deed.business?.name || 'Unknown'}${c.reset}`);
994
+ }
995
+ if (report.errors.length > 0) {
996
+ console.log(` ${c.red}Errors: ${report.errors.join(', ')}${c.reset}`);
997
+ }
998
+ } catch (err) {
999
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
1000
+ process.exit(1);
1001
+ }
1002
+ return;
1003
+ }
1004
+
1005
+ if (sub === 'export') {
1006
+ const { BusinessDeed } = await import('./vault/deed.js');
1007
+ const deed = new BusinessDeed();
1008
+
1009
+ const from = getFlag(args, '--from');
1010
+ if (!from) { console.log(`${c.red}Usage: deed export --from <file> --name <name> --passphrase <pass>${c.reset}`); process.exit(1); }
1011
+
1012
+ const name = getFlag(args, '--name') || await promptInput('Business name: ');
1013
+ const passphrase = getFlag(args, '--passphrase') || await promptInput('Passphrase: ');
1014
+ const output = getFlag(args, '--output');
1015
+
1016
+ console.log(`\n${c.bright}Exporting Business Deed from ${from}...${c.reset}`);
1017
+ try {
1018
+ const result = await deed.export(
1019
+ { envFile: from, jsonFile: from, csvFile: from },
1020
+ { name, passphrase, output }
1021
+ );
1022
+
1023
+ console.log(`\n${c.green}✓ Business Deed exported${c.reset}`);
1024
+ console.log(` Business: ${c.cyan}${name}${c.reset}`);
1025
+ console.log(` Transfer ID: ${c.cyan}${result.transferId}${c.reset}`);
1026
+ console.log(` Services: ${result.services.join(', ') || 'none'}`);
1027
+ console.log(` Credentials: ${result.credentialCount}`);
1028
+ console.log(` File: ${c.yellow}${result.file}${c.reset}`);
1029
+ } catch (err) {
1030
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
1031
+ process.exit(1);
1032
+ }
1033
+ return;
1034
+ }
1035
+
1036
+ console.log(`${c.red}Unknown deed command: ${sub}${c.reset}`);
1037
+ console.log(`Run ${c.cyan}0nmcp deed help${c.reset} for usage.`);
1038
+ }
1039
+
457
1040
  async function handleEngine(args) {
458
1041
  const sub = args[0];
459
1042
 
@@ -913,4 +1496,125 @@ async function handleApp(args) {
913
1496
  process.exit(1);
914
1497
  }
915
1498
 
1499
+ async function startShell() {
1500
+ const cmds = loadCommands();
1501
+ const aliases = [...cmds.keys()];
1502
+ const builtins = ['help', 'commands', 'list', 'exit', 'quit'];
1503
+ const allCompletions = [...builtins, ...aliases];
1504
+
1505
+ console.log(`${c.bright}0nMCP Interactive Shell${c.reset}`);
1506
+ 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`);
1507
+
1508
+ if (aliases.length > 0) {
1509
+ console.log(`${c.bright}Available commands:${c.reset} ${aliases.map(a => c.cyan + '/' + a + c.reset).join(', ')}\n`);
1510
+ }
1511
+
1512
+ const rl = readline.createInterface({
1513
+ input: process.stdin,
1514
+ output: process.stdout,
1515
+ prompt: `${c.green}0n>${c.reset} `,
1516
+ completer: (line) => {
1517
+ const input = line.startsWith('/') ? line.slice(1) : line;
1518
+ const hits = allCompletions.filter(c2 => c2.startsWith(input));
1519
+ return [hits.map(h => '/' + h), line];
1520
+ },
1521
+ });
1522
+
1523
+ rl.prompt();
1524
+
1525
+ rl.on('line', async (line) => {
1526
+ const input = line.trim();
1527
+ if (!input) {
1528
+ rl.prompt();
1529
+ return;
1530
+ }
1531
+
1532
+ // Strip leading / if present
1533
+ const raw = input.startsWith('/') ? input.slice(1) : input;
1534
+ const parts = raw.split(/\s+/);
1535
+ const cmd = parts[0];
1536
+ const cmdArgs = parts.slice(1);
1537
+
1538
+ if (cmd === 'exit' || cmd === 'quit') {
1539
+ console.log(`\n${c.cyan}Goodbye!${c.reset}`);
1540
+ rl.close();
1541
+ process.exit(0);
1542
+ }
1543
+
1544
+ if (cmd === 'help') {
1545
+ console.log(`\n${c.bright}Shell Commands:${c.reset}`);
1546
+ console.log(` ${c.cyan}/commands${c.reset} List all named RUNs`);
1547
+ console.log(` ${c.cyan}/list${c.reset} Show connected services`);
1548
+ console.log(` ${c.cyan}/exit${c.reset} Exit the shell`);
1549
+ if (aliases.length > 0) {
1550
+ console.log(`\n${c.bright}Named RUNs:${c.reset}`);
1551
+ for (const a of aliases) {
1552
+ const def = cmds.get(a);
1553
+ console.log(` ${c.cyan}/${a}${c.reset} ${def.description || def.name || ''}`);
1554
+ }
1555
+ }
1556
+ console.log('');
1557
+ rl.prompt();
1558
+ return;
1559
+ }
1560
+
1561
+ if (cmd === 'commands') {
1562
+ if (aliases.length === 0) {
1563
+ console.log(`\n${c.yellow}No commands defined in SWITCH file.${c.reset}\n`);
1564
+ } else {
1565
+ console.log(`\n${c.bright}Named RUNs:${c.reset}`);
1566
+ for (const a of aliases) {
1567
+ const def = cmds.get(a);
1568
+ console.log(` ${c.cyan}/${a}${c.reset} ${def.description || def.name || ''}`);
1569
+ }
1570
+ console.log('');
1571
+ }
1572
+ rl.prompt();
1573
+ return;
1574
+ }
1575
+
1576
+ if (cmd === 'list') {
1577
+ const connectionsDir = path.join(DOT_ON_DIR, 'connections');
1578
+ if (fs.existsSync(connectionsDir)) {
1579
+ const files = fs.readdirSync(connectionsDir).filter(f => f.endsWith('.0n'));
1580
+ console.log(`\n${c.bright}Connected Services:${c.reset}`);
1581
+ for (const file of files) {
1582
+ try {
1583
+ const data = JSON.parse(fs.readFileSync(path.join(connectionsDir, file), 'utf8'));
1584
+ console.log(` ${c.green}●${c.reset} ${data.$0n?.name || data.service}`);
1585
+ } catch {
1586
+ console.log(` ${c.red}●${c.reset} ${file} (error)`);
1587
+ }
1588
+ }
1589
+ console.log('');
1590
+ } else {
1591
+ console.log(`\n${c.yellow}No connections found.${c.reset}\n`);
1592
+ }
1593
+ rl.prompt();
1594
+ return;
1595
+ }
1596
+
1597
+ // Try to run as a named command
1598
+ if (cmds.has(cmd)) {
1599
+ console.log('');
1600
+ try {
1601
+ await runCommand(cmd, cmds.get(cmd), cmdArgs);
1602
+ } catch (err) {
1603
+ console.log(`${c.red}Error: ${err.message}${c.reset}`);
1604
+ }
1605
+ console.log('');
1606
+ rl.prompt();
1607
+ return;
1608
+ }
1609
+
1610
+ console.log(`${c.yellow}Unknown command: ${cmd}${c.reset}`);
1611
+ console.log(`Type ${c.cyan}/help${c.reset} for available commands.`);
1612
+ rl.prompt();
1613
+ });
1614
+
1615
+ rl.on('close', () => {
1616
+ process.exit(0);
1617
+ });
1618
+ }
1619
+
916
1620
  main().catch(console.error);