0nmcp 2.0.0 → 2.2.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 +162 -60
- package/catalog.js +756 -0
- package/cli.js +274 -0
- package/index.js +8 -1
- package/lib/stats.json +1 -1
- package/package.json +44 -2
- package/vault/deed-collector.js +286 -0
- package/vault/deed-importer.js +277 -0
- package/vault/deed.js +319 -0
- package/vault/tools-deed.js +257 -0
package/cli.js
CHANGED
|
@@ -271,6 +271,13 @@ ${c.bright}Links:${c.reset}
|
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// Business Deed
|
|
275
|
+
if (command === 'deed') {
|
|
276
|
+
console.log(BANNER);
|
|
277
|
+
await handleDeed(args.slice(1));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
274
281
|
// Engine
|
|
275
282
|
if (command === 'engine') {
|
|
276
283
|
console.log(BANNER);
|
|
@@ -763,6 +770,273 @@ ${c.bright}7 Semantic Layers:${c.reset}
|
|
|
763
770
|
console.log(`Run ${c.cyan}0nmcp vault help${c.reset} for usage.`);
|
|
764
771
|
}
|
|
765
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
|
+
|
|
766
1040
|
async function handleEngine(args) {
|
|
767
1041
|
const sub = args[0];
|
|
768
1042
|
|
package/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import { registerAllTools } from "./tools.js";
|
|
|
29
29
|
import { registerCrmTools } from "./crm/index.js";
|
|
30
30
|
import { registerVaultTools, autoUnseal } from "./vault/index.js";
|
|
31
31
|
import { registerContainerTools } from "./vault/tools-container.js";
|
|
32
|
+
import { registerDeedTools } from "./vault/tools-deed.js";
|
|
32
33
|
import { unsealedCache } from "./vault/cache.js";
|
|
33
34
|
import { registerEngineTools } from "./engine/index.js";
|
|
34
35
|
|
|
@@ -40,7 +41,7 @@ const workflowRunner = new WorkflowRunner(connections);
|
|
|
40
41
|
|
|
41
42
|
const server = new McpServer({
|
|
42
43
|
name: "0nMCP",
|
|
43
|
-
version: "2.
|
|
44
|
+
version: "2.1.0",
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
// ============================================================
|
|
@@ -80,6 +81,12 @@ registerEngineTools(server, z);
|
|
|
80
81
|
|
|
81
82
|
registerContainerTools(server, z);
|
|
82
83
|
|
|
84
|
+
// ============================================================
|
|
85
|
+
// BUSINESS DEED TOOLS (digital asset transfer system)
|
|
86
|
+
// ============================================================
|
|
87
|
+
|
|
88
|
+
registerDeedTools(server, z);
|
|
89
|
+
|
|
83
90
|
// ============================================================
|
|
84
91
|
// START SERVER (stdio transport)
|
|
85
92
|
// ============================================================
|
package/lib/stats.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0nmcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"mcpName": "io.github.0nork/0nMCP",
|
|
5
5
|
"description": "Universal AI API Orchestrator — 550 tools, 26 services, portable AI Brain bundles + machine-bound vault encryption + Application Engine. The most comprehensive MCP server available. Free and open source from 0nORK.",
|
|
6
6
|
"type": "module",
|
|
@@ -73,6 +73,15 @@
|
|
|
73
73
|
},
|
|
74
74
|
"./vault/seal": {
|
|
75
75
|
"import": "./vault/seal.js"
|
|
76
|
+
},
|
|
77
|
+
"./vault/deed": {
|
|
78
|
+
"import": "./vault/deed.js"
|
|
79
|
+
},
|
|
80
|
+
"./vault/deed-collector": {
|
|
81
|
+
"import": "./vault/deed-collector.js"
|
|
82
|
+
},
|
|
83
|
+
"./vault/deed-importer": {
|
|
84
|
+
"import": "./vault/deed-importer.js"
|
|
76
85
|
}
|
|
77
86
|
},
|
|
78
87
|
"scripts": {
|
|
@@ -154,6 +163,39 @@
|
|
|
154
163
|
"sha3",
|
|
155
164
|
"argon2",
|
|
156
165
|
"digital-asset-transfer",
|
|
166
|
+
"business-deed-transfer",
|
|
167
|
+
"workflow-runtime",
|
|
168
|
+
"http-server",
|
|
169
|
+
"cli",
|
|
170
|
+
"application-engine",
|
|
171
|
+
"container",
|
|
172
|
+
"mcp-registry",
|
|
173
|
+
"quickbooks",
|
|
174
|
+
"asana",
|
|
175
|
+
"intercom",
|
|
176
|
+
"dropbox",
|
|
177
|
+
"whatsapp",
|
|
178
|
+
"instagram",
|
|
179
|
+
"twitter",
|
|
180
|
+
"x-ads",
|
|
181
|
+
"tiktok",
|
|
182
|
+
"google-ads",
|
|
183
|
+
"facebook-ads",
|
|
184
|
+
"plaid",
|
|
185
|
+
"square",
|
|
186
|
+
"tiktok-ads",
|
|
187
|
+
"linkedin-ads",
|
|
188
|
+
"instagram-ads",
|
|
189
|
+
"smartlead",
|
|
190
|
+
"zapier",
|
|
191
|
+
"mulesoft",
|
|
192
|
+
"azure",
|
|
193
|
+
"pipedrive",
|
|
194
|
+
"linkedin",
|
|
195
|
+
"advertising",
|
|
196
|
+
"cold-email",
|
|
197
|
+
"finance",
|
|
198
|
+
"accounting",
|
|
157
199
|
"0n",
|
|
158
200
|
"0nork",
|
|
159
201
|
"0nmcp"
|
|
@@ -227,6 +269,6 @@
|
|
|
227
269
|
"triggers": 93,
|
|
228
270
|
"totalCapabilities": 708,
|
|
229
271
|
"categories": 13,
|
|
230
|
-
"lastUpdated": "2026-02-
|
|
272
|
+
"lastUpdated": "2026-02-28T10:15:52.734Z"
|
|
231
273
|
}
|
|
232
274
|
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — Vault: Deed Credential Collector
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Collects credentials from multiple sources (files, dirs,
|
|
5
|
+
// manual entry) and auto-detects services using the engine
|
|
6
|
+
// mapper. Feeds into BusinessDeed.create().
|
|
7
|
+
//
|
|
8
|
+
// Patent Pending: US Provisional Patent Application #63/990,046
|
|
9
|
+
// ============================================================
|
|
10
|
+
|
|
11
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
12
|
+
import { join, extname } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { parseFile, parseEnvString, parseJsonString, parseCsvString } from "../engine/parser.js";
|
|
15
|
+
import { mapEnvVars, groupByService } from "../engine/mapper.js";
|
|
16
|
+
import { verifyCredentials } from "../engine/validator.js";
|
|
17
|
+
|
|
18
|
+
const CONNECTIONS_DIR = join(homedir(), ".0n", "connections");
|
|
19
|
+
|
|
20
|
+
// ── Collect from .0n connection files ───────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load credentials from ~/.0n/connections/*.0n files.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} [dir] - Connections directory (default: ~/.0n/connections/)
|
|
26
|
+
* @returns {{ credentials: Object, services: string[] }}
|
|
27
|
+
*/
|
|
28
|
+
export function collectFromConnections(dir = CONNECTIONS_DIR) {
|
|
29
|
+
const credentials = {};
|
|
30
|
+
const services = [];
|
|
31
|
+
|
|
32
|
+
if (!existsSync(dir)) return { credentials, services };
|
|
33
|
+
|
|
34
|
+
const files = readdirSync(dir).filter(f => f.endsWith(".0n"));
|
|
35
|
+
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
try {
|
|
38
|
+
const content = readFileSync(join(dir, file), "utf-8");
|
|
39
|
+
const data = JSON.parse(content);
|
|
40
|
+
|
|
41
|
+
// .0n connection file format: { $0n: { type, service }, credentials: { ... } }
|
|
42
|
+
const service = data?.$0n?.service || file.replace(".0n", "");
|
|
43
|
+
if (data.credentials && typeof data.credentials === "object") {
|
|
44
|
+
credentials[service] = data.credentials;
|
|
45
|
+
services.push(service);
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// Skip invalid files
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { credentials, services };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Collect from file (auto-detect format) ──────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Collect credentials from a file (.env, .json, .csv).
|
|
59
|
+
*
|
|
60
|
+
* @param {string} filePath
|
|
61
|
+
* @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
|
|
62
|
+
*/
|
|
63
|
+
export function collectFromFile(filePath) {
|
|
64
|
+
const { entries } = parseFile(filePath);
|
|
65
|
+
return processEntries(entries);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Collect from string content ─────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Collect from raw .env string content.
|
|
72
|
+
* @param {string} content
|
|
73
|
+
* @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
|
|
74
|
+
*/
|
|
75
|
+
export function collectFromEnvString(content) {
|
|
76
|
+
const entries = parseEnvString(content);
|
|
77
|
+
return processEntries(entries);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Collect from raw JSON string content.
|
|
82
|
+
* @param {string} content
|
|
83
|
+
* @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
|
|
84
|
+
*/
|
|
85
|
+
export function collectFromJsonString(content) {
|
|
86
|
+
const entries = parseJsonString(content);
|
|
87
|
+
return processEntries(entries);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Collect from raw CSV string content.
|
|
92
|
+
* @param {string} content
|
|
93
|
+
* @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
|
|
94
|
+
*/
|
|
95
|
+
export function collectFromCsvString(content) {
|
|
96
|
+
const entries = parseCsvString(content);
|
|
97
|
+
return processEntries(entries);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Collect from manual key-value pairs ─────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Collect from manual key-value pairs.
|
|
104
|
+
*
|
|
105
|
+
* @param {Array<{ key: string, value: string }>} pairs
|
|
106
|
+
* @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
|
|
107
|
+
*/
|
|
108
|
+
export function collectFromManual(pairs) {
|
|
109
|
+
return processEntries(pairs);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Collect from pre-structured service credentials ─────────
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Collect from already-structured credentials object.
|
|
116
|
+
* Format: { stripe: { apiKey: "sk_..." }, github: { token: "ghp_..." } }
|
|
117
|
+
*
|
|
118
|
+
* @param {Object} structured
|
|
119
|
+
* @returns {{ credentials: Object, services: string[] }}
|
|
120
|
+
*/
|
|
121
|
+
export function collectFromStructured(structured) {
|
|
122
|
+
const services = Object.keys(structured);
|
|
123
|
+
return { credentials: structured, services };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Process entries through mapper ──────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Process raw key-value entries through the mapper pipeline.
|
|
130
|
+
*
|
|
131
|
+
* @param {Array<{ key: string, value: string }>} entries
|
|
132
|
+
* @returns {{ credentials: Object, envVars: Object, services: string[], unmapped: Array }}
|
|
133
|
+
*/
|
|
134
|
+
function processEntries(entries) {
|
|
135
|
+
const { mapped, unmapped } = mapEnvVars(entries);
|
|
136
|
+
const grouped = groupByService(mapped);
|
|
137
|
+
|
|
138
|
+
// Build credentials and envVars
|
|
139
|
+
const credentials = {};
|
|
140
|
+
const envVars = {};
|
|
141
|
+
const services = [];
|
|
142
|
+
|
|
143
|
+
for (const [service, group] of Object.entries(grouped)) {
|
|
144
|
+
credentials[service] = group.credentials;
|
|
145
|
+
services.push(service);
|
|
146
|
+
for (const envVar of group.envVars) {
|
|
147
|
+
const entry = entries.find(e => e.key === envVar);
|
|
148
|
+
if (entry) envVars[entry.key] = entry.value;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Unmapped entries go to envVars
|
|
153
|
+
for (const entry of unmapped) {
|
|
154
|
+
envVars[entry.key] = entry.value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { credentials, envVars, services, unmapped };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Validate credentials live ───────────────────────────────
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validate collected credentials against live API endpoints.
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} credentials - { service: { field: value } }
|
|
166
|
+
* @returns {Promise<Object>} Validation results per service
|
|
167
|
+
*/
|
|
168
|
+
export async function validateCollected(credentials) {
|
|
169
|
+
const results = {};
|
|
170
|
+
const summary = { total: 0, valid: 0, invalid: 0, skipped: 0 };
|
|
171
|
+
|
|
172
|
+
const entries = Object.entries(credentials);
|
|
173
|
+
summary.total = entries.length;
|
|
174
|
+
|
|
175
|
+
// Validate in parallel batches of 5
|
|
176
|
+
const batchSize = 5;
|
|
177
|
+
for (let i = 0; i < entries.length; i += batchSize) {
|
|
178
|
+
const batch = entries.slice(i, i + batchSize);
|
|
179
|
+
const promises = batch.map(([service, creds]) =>
|
|
180
|
+
verifyCredentials(service, creds).then(r => ({ service, ...r }))
|
|
181
|
+
);
|
|
182
|
+
const batchResults = await Promise.allSettled(promises);
|
|
183
|
+
|
|
184
|
+
for (const result of batchResults) {
|
|
185
|
+
if (result.status === "fulfilled") {
|
|
186
|
+
const r = result.value;
|
|
187
|
+
results[r.service] = r;
|
|
188
|
+
if (r.skipped) summary.skipped++;
|
|
189
|
+
else if (r.valid) summary.valid++;
|
|
190
|
+
else summary.invalid++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { results, summary };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Master collection function ──────────────────────────────
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Collect credentials from multiple sources at once.
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} sources
|
|
204
|
+
* @param {string} [sources.envFile] - Path to .env file
|
|
205
|
+
* @param {string} [sources.jsonFile] - Path to JSON file
|
|
206
|
+
* @param {string} [sources.csvFile] - Path to CSV file
|
|
207
|
+
* @param {string} [sources.connectionsDir] - Path to connections dir
|
|
208
|
+
* @param {Array} [sources.manual] - Manual key-value pairs
|
|
209
|
+
* @param {Object} [sources.structured] - Pre-structured credentials
|
|
210
|
+
* @param {boolean} [sources.validate] - Validate credentials live
|
|
211
|
+
* @returns {Promise<Object>}
|
|
212
|
+
*/
|
|
213
|
+
export async function collectCredentials(sources) {
|
|
214
|
+
const allCredentials = {};
|
|
215
|
+
const allEnvVars = {};
|
|
216
|
+
const allServices = new Set();
|
|
217
|
+
const allUnmapped = [];
|
|
218
|
+
|
|
219
|
+
// Collect from each source
|
|
220
|
+
if (sources.envFile && existsSync(sources.envFile)) {
|
|
221
|
+
const result = collectFromFile(sources.envFile);
|
|
222
|
+
mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (sources.jsonFile && existsSync(sources.jsonFile)) {
|
|
226
|
+
const result = collectFromFile(sources.jsonFile);
|
|
227
|
+
mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (sources.csvFile && existsSync(sources.csvFile)) {
|
|
231
|
+
const result = collectFromFile(sources.csvFile);
|
|
232
|
+
mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (sources.connectionsDir || existsSync(CONNECTIONS_DIR)) {
|
|
236
|
+
const dir = sources.connectionsDir || CONNECTIONS_DIR;
|
|
237
|
+
const result = collectFromConnections(dir);
|
|
238
|
+
for (const [svc, creds] of Object.entries(result.credentials)) {
|
|
239
|
+
allCredentials[svc] = { ...allCredentials[svc], ...creds };
|
|
240
|
+
allServices.add(svc);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (sources.manual && Array.isArray(sources.manual)) {
|
|
245
|
+
const result = collectFromManual(sources.manual);
|
|
246
|
+
mergeResults(allCredentials, allEnvVars, allServices, allUnmapped, result);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (sources.structured && typeof sources.structured === "object") {
|
|
250
|
+
const result = collectFromStructured(sources.structured);
|
|
251
|
+
for (const [svc, creds] of Object.entries(result.credentials)) {
|
|
252
|
+
allCredentials[svc] = { ...allCredentials[svc], ...creds };
|
|
253
|
+
allServices.add(svc);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const output = {
|
|
258
|
+
credentials: allCredentials,
|
|
259
|
+
envVars: allEnvVars,
|
|
260
|
+
services: Array.from(allServices),
|
|
261
|
+
unmapped: allUnmapped,
|
|
262
|
+
credentialCount: Object.values(allCredentials).reduce(
|
|
263
|
+
(sum, svc) => sum + Object.keys(svc).length, 0
|
|
264
|
+
),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Optional live validation
|
|
268
|
+
if (sources.validate) {
|
|
269
|
+
output.validation = await validateCollected(allCredentials);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return output;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Merge helper ────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
function mergeResults(allCreds, allEnv, allServices, allUnmapped, result) {
|
|
278
|
+
for (const [svc, creds] of Object.entries(result.credentials || {})) {
|
|
279
|
+
allCreds[svc] = { ...allCreds[svc], ...creds };
|
|
280
|
+
allServices.add(svc);
|
|
281
|
+
}
|
|
282
|
+
for (const [k, v] of Object.entries(result.envVars || {})) {
|
|
283
|
+
allEnv[k] = v;
|
|
284
|
+
}
|
|
285
|
+
if (result.unmapped) allUnmapped.push(...result.unmapped);
|
|
286
|
+
}
|