@epicai/legion 1.0.1 → 1.0.2
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.
Potentially problematic release.
This version of @epicai/legion might be problematic. Click here for more details.
- package/dist/bin/setup.js +23 -278
- package/package.json +1 -1
package/dist/bin/setup.js
CHANGED
|
@@ -918,288 +918,35 @@ async function runSetupWizard() {
|
|
|
918
918
|
p.log.success(`Using ${system.localBackend} on port ${system.localPort}`);
|
|
919
919
|
configuredClients.push('local');
|
|
920
920
|
}
|
|
921
|
-
// Step 3:
|
|
922
|
-
const
|
|
923
|
-
|
|
921
|
+
// Step 3: Zero-credential live demo
|
|
922
|
+
const ZERO_CRED = [
|
|
923
|
+
{ id: 'searchcode', name: 'Searchcode', desc: 'Search 75 billion lines of open source code', tools: 6 },
|
|
924
|
+
{ id: 'com-claude-mcp-pubmed-pubmed', name: 'PubMed', desc: 'Search 36 million biomedical research papers', tools: 7 },
|
|
925
|
+
{ id: 'robtex', name: 'Robtex', desc: 'Network intelligence — DNS, IP, ASN lookups', tools: 45 },
|
|
926
|
+
{ id: 'govbase-mcp', name: 'Govbase', desc: 'Government data — legislators, bills, committees', tools: 10 },
|
|
927
|
+
];
|
|
928
|
+
const demoChoice = await p.select({
|
|
929
|
+
message: 'Try a live data connection? These require no credentials:',
|
|
924
930
|
options: [
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
{ value: '
|
|
931
|
-
{ value: 'manual', label: 'I\'ll enter them manually' },
|
|
931
|
+
...ZERO_CRED.map(a => ({
|
|
932
|
+
value: a.id,
|
|
933
|
+
label: `${a.name}`,
|
|
934
|
+
hint: `${a.desc} — ${a.tools} tools`,
|
|
935
|
+
})),
|
|
936
|
+
{ value: 'skip', label: 'Skip for now', hint: 'add adapters later with: npx @epicai/legion add <name>' },
|
|
932
937
|
],
|
|
933
938
|
});
|
|
934
|
-
if (p.isCancel(
|
|
939
|
+
if (p.isCancel(demoChoice)) {
|
|
935
940
|
p.cancel('Setup cancelled.');
|
|
936
941
|
process.exit(0);
|
|
937
942
|
}
|
|
938
|
-
let vaultAddr = '';
|
|
939
|
-
let vaultToken = '';
|
|
940
|
-
let vaultConnected = false;
|
|
941
|
-
if (secretsChoice === 'vault') {
|
|
942
|
-
const envAddr = process.env.VAULT_ADDR;
|
|
943
|
-
const envToken = process.env.VAULT_TOKEN;
|
|
944
|
-
if (envAddr && envToken) {
|
|
945
|
-
p.log.success(`Detected VAULT_ADDR=${envAddr}`);
|
|
946
|
-
vaultAddr = envAddr;
|
|
947
|
-
vaultToken = envToken;
|
|
948
|
-
}
|
|
949
|
-
else {
|
|
950
|
-
const addr = await p.text({
|
|
951
|
-
message: 'Vault address',
|
|
952
|
-
placeholder: 'https://vault.example.com:8200',
|
|
953
|
-
validate: (v) => { if (!v)
|
|
954
|
-
return 'Required'; if (!v.startsWith('http'))
|
|
955
|
-
return 'Must start with http:// or https://'; },
|
|
956
|
-
});
|
|
957
|
-
if (p.isCancel(addr)) {
|
|
958
|
-
p.cancel('Setup cancelled.');
|
|
959
|
-
process.exit(0);
|
|
960
|
-
}
|
|
961
|
-
const token = await p.password({ message: 'Vault token', validate: (v) => { if (!v)
|
|
962
|
-
return 'Required'; } });
|
|
963
|
-
if (p.isCancel(token)) {
|
|
964
|
-
p.cancel('Setup cancelled.');
|
|
965
|
-
process.exit(0);
|
|
966
|
-
}
|
|
967
|
-
vaultAddr = addr;
|
|
968
|
-
vaultToken = token;
|
|
969
|
-
}
|
|
970
|
-
s.start('Connecting to Vault');
|
|
971
|
-
try {
|
|
972
|
-
const controller = new AbortController();
|
|
973
|
-
const t = setTimeout(() => controller.abort(), 5000);
|
|
974
|
-
const resp = await fetch(`${vaultAddr}/v1/sys/health`, { headers: { 'X-Vault-Token': vaultToken }, signal: controller.signal });
|
|
975
|
-
clearTimeout(t);
|
|
976
|
-
if (resp.ok) {
|
|
977
|
-
vaultConnected = true;
|
|
978
|
-
s.stop(`Connected to Vault at ${vaultAddr}`);
|
|
979
|
-
writeCredential('VAULT_ADDR', vaultAddr);
|
|
980
|
-
writeCredential('VAULT_TOKEN', vaultToken);
|
|
981
|
-
}
|
|
982
|
-
else {
|
|
983
|
-
s.stop(`Vault returned ${resp.status} — check your token`);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
catch {
|
|
987
|
-
s.stop(`Cannot reach Vault at ${vaultAddr}`);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
if (secretsChoice === '1password') {
|
|
991
|
-
try {
|
|
992
|
-
execSync('op --version', { stdio: 'pipe' });
|
|
993
|
-
p.log.success('1Password CLI detected');
|
|
994
|
-
}
|
|
995
|
-
catch {
|
|
996
|
-
p.log.warning('1Password CLI not found — install from https://1password.com/downloads/command-line');
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
if (secretsChoice === 'doppler') {
|
|
1000
|
-
try {
|
|
1001
|
-
execSync('doppler --version', { stdio: 'pipe' });
|
|
1002
|
-
p.log.success('Doppler CLI detected');
|
|
1003
|
-
}
|
|
1004
|
-
catch {
|
|
1005
|
-
p.log.warning('Doppler CLI not found — install from https://docs.doppler.com/docs/install-cli');
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
// Step 4: Auto-wire adapters from credentials
|
|
1009
943
|
const selectedAdapterIds = [];
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const restKey = adapter.rest?.envKey;
|
|
1015
|
-
const mcpKeys = adapter.mcp?.envKeys || [];
|
|
1016
|
-
const allKeys = [...(restKey ? [restKey] : []), ...mcpKeys];
|
|
1017
|
-
if (allKeys.length > 0 && allKeys.every(k => !!existingCreds[k])) {
|
|
1018
|
-
autoMatched.push(adapter);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
if (autoMatched.length > 0) {
|
|
1022
|
-
p.note(autoMatched.map(a => `${pc.green('✓')} ${a.name || a.id}`).join('\n'), `${autoMatched.length} adapter${autoMatched.length !== 1 ? 's' : ''} matched from your credentials`);
|
|
1023
|
-
selectedAdapterIds.push(...autoMatched.map(a => a.id));
|
|
1024
|
-
}
|
|
1025
|
-
else {
|
|
1026
|
-
p.log.info('No adapters matched from credentials — use ' + pc.cyan('npx @epicai/legion add <name>') + ' to connect adapters.');
|
|
1027
|
-
}
|
|
1028
|
-
// Optional: search for additional adapters
|
|
1029
|
-
const addMore = await p.confirm({
|
|
1030
|
-
message: 'Search for additional adapters to configure?',
|
|
1031
|
-
initialValue: false,
|
|
1032
|
-
});
|
|
1033
|
-
if (!p.isCancel(addMore) && addMore) {
|
|
1034
|
-
let searching = true;
|
|
1035
|
-
while (searching) {
|
|
1036
|
-
const searchTerm = await p.text({
|
|
1037
|
-
message: 'Search adapters (name or keyword)',
|
|
1038
|
-
placeholder: 'e.g. grafana, paypal, github',
|
|
1039
|
-
});
|
|
1040
|
-
if (p.isCancel(searchTerm) || !searchTerm)
|
|
1041
|
-
break;
|
|
1042
|
-
const term = searchTerm.toLowerCase();
|
|
1043
|
-
const matches = allAdapters.filter(a => !selectedAdapterIds.includes(a.id) && (a.id.includes(term) ||
|
|
1044
|
-
(a.name || '').toLowerCase().includes(term) ||
|
|
1045
|
-
(a.description || '').toLowerCase().includes(term) ||
|
|
1046
|
-
(a.category || '').includes(term)));
|
|
1047
|
-
if (matches.length === 0) {
|
|
1048
|
-
p.log.warning(`No adapters found for "${searchTerm}"`);
|
|
1049
|
-
}
|
|
1050
|
-
else {
|
|
1051
|
-
const options = matches.slice(0, 50).map(a => ({
|
|
1052
|
-
value: a.id,
|
|
1053
|
-
label: a.name || a.id,
|
|
1054
|
-
hint: a.description?.slice(0, 60),
|
|
1055
|
-
}));
|
|
1056
|
-
const picked = await p.multiselect({
|
|
1057
|
-
message: `${matches.length} match${matches.length !== 1 ? 'es' : ''} — select to add`,
|
|
1058
|
-
options,
|
|
1059
|
-
required: false,
|
|
1060
|
-
});
|
|
1061
|
-
if (!p.isCancel(picked)) {
|
|
1062
|
-
selectedAdapterIds.push(...picked);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
const cont = await p.confirm({ message: 'Search for more?', initialValue: false });
|
|
1066
|
-
if (p.isCancel(cont) || !cont)
|
|
1067
|
-
searching = false;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
// Step 5: Credentials
|
|
1071
|
-
if (selectedAdapterIds.length > 0) {
|
|
1072
|
-
if (secretsChoice === 'vault' && vaultConnected) {
|
|
1073
|
-
s.start('Pulling credentials from Vault');
|
|
1074
|
-
let pulled = 0;
|
|
1075
|
-
for (const adapterId of selectedAdapterIds) {
|
|
1076
|
-
try {
|
|
1077
|
-
const controller = new AbortController();
|
|
1078
|
-
const t = setTimeout(() => controller.abort(), 3000);
|
|
1079
|
-
const resp = await fetch(`${vaultAddr}/v1/secret/data/${adapterId}`, {
|
|
1080
|
-
headers: { 'X-Vault-Token': vaultToken },
|
|
1081
|
-
signal: controller.signal,
|
|
1082
|
-
});
|
|
1083
|
-
clearTimeout(t);
|
|
1084
|
-
if (resp.ok) {
|
|
1085
|
-
const data = await resp.json();
|
|
1086
|
-
const secrets = data?.data?.data;
|
|
1087
|
-
if (secrets) {
|
|
1088
|
-
for (const [k, v] of Object.entries(secrets)) {
|
|
1089
|
-
writeCredential(k, v);
|
|
1090
|
-
}
|
|
1091
|
-
pulled++;
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
catch { /* skip */ }
|
|
1096
|
-
}
|
|
1097
|
-
s.stop(`Pulled credentials for ${pulled} of ${selectedAdapterIds.length} adapters`);
|
|
1098
|
-
}
|
|
1099
|
-
else if (secretsChoice === '1password') {
|
|
1100
|
-
s.start('Pulling credentials from 1Password');
|
|
1101
|
-
let pulled = 0;
|
|
1102
|
-
for (const adapterId of selectedAdapterIds) {
|
|
1103
|
-
try {
|
|
1104
|
-
const result = execSync(`op item get "${adapterId}" --format json 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 });
|
|
1105
|
-
const item = JSON.parse(result);
|
|
1106
|
-
const fields = item.fields || [];
|
|
1107
|
-
for (const field of fields) {
|
|
1108
|
-
if (field.value && field.label) {
|
|
1109
|
-
const envKey = `${adapterId.toUpperCase().replace(/-/g, '_')}_${field.label.toUpperCase().replace(/\s+/g, '_')}`;
|
|
1110
|
-
writeCredential(envKey, field.value);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
pulled++;
|
|
1114
|
-
}
|
|
1115
|
-
catch { /* skip */ }
|
|
1116
|
-
}
|
|
1117
|
-
s.stop(`Pulled credentials for ${pulled} of ${selectedAdapterIds.length} adapters`);
|
|
1118
|
-
}
|
|
1119
|
-
else if (secretsChoice === 'doppler') {
|
|
1120
|
-
s.start('Pulling credentials from Doppler');
|
|
1121
|
-
try {
|
|
1122
|
-
const result = execSync('doppler secrets download --no-file --format json 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });
|
|
1123
|
-
const secrets = JSON.parse(result);
|
|
1124
|
-
for (const [k, v] of Object.entries(secrets)) {
|
|
1125
|
-
if (typeof v === 'string')
|
|
1126
|
-
writeCredential(k, v);
|
|
1127
|
-
}
|
|
1128
|
-
s.stop('Credentials pulled from Doppler');
|
|
1129
|
-
}
|
|
1130
|
-
catch {
|
|
1131
|
-
s.stop('Doppler pull failed — configure with: doppler setup');
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
else if (secretsChoice === 'manual') {
|
|
1135
|
-
for (const adapterId of selectedAdapterIds) {
|
|
1136
|
-
const adapter = allAdapters.find(a => a.id === adapterId);
|
|
1137
|
-
if (!adapter)
|
|
1138
|
-
continue;
|
|
1139
|
-
const envKey = adapter.rest?.envKey || `${adapterId.toUpperCase().replace(/-/g, '_')}_API_KEY`;
|
|
1140
|
-
const key = await p.password({ message: `${envKey} for ${adapter.name || adapterId}` });
|
|
1141
|
-
if (!p.isCancel(key) && key) {
|
|
1142
|
-
writeCredential(envKey, key);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
else if (secretsChoice === 'env') {
|
|
1147
|
-
p.log.info('Using credentials from environment variables');
|
|
1148
|
-
}
|
|
1149
|
-
// Step 6: Install stdio dependencies
|
|
1150
|
-
for (const adapterId of selectedAdapterIds) {
|
|
1151
|
-
const adapter = allAdapters.find(a => a.id === adapterId);
|
|
1152
|
-
if (!adapter?.mcp?.packageName || adapter.mcp.transport !== 'stdio')
|
|
1153
|
-
continue;
|
|
1154
|
-
s.start(`Installing ${adapter.name || adapterId}`);
|
|
1155
|
-
try {
|
|
1156
|
-
execSync(`npm install -g --ignore-scripts ${adapter.mcp.packageName}`, { stdio: 'pipe', timeout: 60000 });
|
|
1157
|
-
// Verify artifact integrity
|
|
1158
|
-
try {
|
|
1159
|
-
const { ArtifactVerifier } = await import('../trust/ArtifactVerifier.js');
|
|
1160
|
-
const verifier = new ArtifactVerifier({ verifyDigests: true });
|
|
1161
|
-
const pkgPath = execSync(`npm root -g`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1162
|
-
const mainFile = join(pkgPath, adapter.mcp.packageName, 'package.json');
|
|
1163
|
-
const result = await verifier.verify(adapter.mcp.packageName, mainFile);
|
|
1164
|
-
if (!result.valid) {
|
|
1165
|
-
s.stop(`${pc.red('✗')} ${adapter.name || adapterId} — integrity check failed`);
|
|
1166
|
-
}
|
|
1167
|
-
else {
|
|
1168
|
-
s.stop(`${pc.green('✓')} ${adapter.name || adapterId} installed — verified`);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
catch {
|
|
1172
|
-
s.stop(`${pc.green('✓')} ${adapter.name || adapterId} installed`);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
catch {
|
|
1176
|
-
s.stop(`${pc.yellow('!')} ${adapter.name || adapterId} — run manually: npm install -g ${adapter.mcp.packageName}`);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
// Step 7: Verify
|
|
1180
|
-
s.start('Verifying connections');
|
|
1181
|
-
const creds = loadCredentials();
|
|
1182
|
-
let verified = 0;
|
|
1183
|
-
const results = [];
|
|
1184
|
-
for (const adapterId of selectedAdapterIds) {
|
|
1185
|
-
const adapter = allAdapters.find(a => a.id === adapterId);
|
|
1186
|
-
if (!adapter)
|
|
1187
|
-
continue;
|
|
1188
|
-
const hasKey = adapter.rest?.envKey ? !!creds[adapter.rest.envKey] : false;
|
|
1189
|
-
if (hasKey) {
|
|
1190
|
-
verified++;
|
|
1191
|
-
results.push(`${pc.green('✓')} ${adapter.name || adapterId} ${adapter.rest?.toolCount || 0} tools`);
|
|
1192
|
-
}
|
|
1193
|
-
else {
|
|
1194
|
-
results.push(`${pc.yellow('○')} ${adapter.name || adapterId} credentials needed`);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
s.stop('Verification complete');
|
|
1198
|
-
if (results.length > 0) {
|
|
1199
|
-
p.note(results.join('\n'), `${verified} of ${selectedAdapterIds.length} adapters ready`);
|
|
1200
|
-
}
|
|
944
|
+
if (demoChoice !== 'skip') {
|
|
945
|
+
const picked = ZERO_CRED.find(a => a.id === demoChoice);
|
|
946
|
+
selectedAdapterIds.push(demoChoice);
|
|
947
|
+
p.log.success(`${picked.name} connected — ${picked.tools} tools ready`);
|
|
1201
948
|
}
|
|
1202
|
-
// Step
|
|
949
|
+
// Step 4: Save state and config
|
|
1203
950
|
const state = loadState();
|
|
1204
951
|
for (const id of selectedAdapterIds) {
|
|
1205
952
|
const adapter = allAdapters.find(a => a.id === id);
|
|
@@ -1213,16 +960,14 @@ async function runSetupWizard() {
|
|
|
1213
960
|
saveState(state);
|
|
1214
961
|
saveConfig({
|
|
1215
962
|
selectedAdapters: selectedAdapterIds,
|
|
1216
|
-
secretsProvider:
|
|
963
|
+
secretsProvider: 'manual',
|
|
1217
964
|
aiClient: configuredClients.join(','),
|
|
1218
965
|
localBackend: system.localBackend || undefined,
|
|
1219
966
|
});
|
|
1220
967
|
// Outro
|
|
1221
968
|
p.note([
|
|
1222
|
-
`
|
|
1223
|
-
`Remove adapters: ${pc.cyan('npx @epicai/legion remove <name>')}`,
|
|
969
|
+
`Connect more: ${pc.cyan('npx @epicai/legion add <name>')}`,
|
|
1224
970
|
`Health check: ${pc.cyan('npx @epicai/legion health')}`,
|
|
1225
|
-
`Update adapters: ${pc.cyan('npx @epicai/legion update')}`,
|
|
1226
971
|
`List adapters: ${pc.cyan('npx @epicai/legion list')}`,
|
|
1227
972
|
].join('\n'), 'Manage your adapters');
|
|
1228
973
|
p.outro(`${pc.green('Legion is ready.')} Your credentials never leave this machine.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicai/legion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Epic AI® Legion — 35,020 tools. One self-hosted MCP server. Intelligent Virtual Assistant (IVA) integration layer for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|