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/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.0.0",
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-02-25T10:54:43.654Z",
2
+ "generated": "2026-02-28T10:15:52.734Z",
3
3
  "catalogVersion": "1.2.2",
4
4
  "services": 26,
5
5
  "tools": 290,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0nmcp",
3
- "version": "2.0.0",
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-25T10:54:43.654Z"
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
+ }