@epicai/legion 1.0.2 → 1.0.3
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/CHANGELOG.md +19 -0
- package/dist/bin/setup.d.ts +9 -6
- package/dist/bin/setup.js +346 -73
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## 1.0.2 — 2026-04-02
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Setup wizard replaced credential onboarding with zero-credential live demo (Searchcode, PubMed, Robtex, Govbase) — developers see real data before touching any secrets
|
|
14
|
+
- `@xenova/transformers` moved to optional peer dependency — eliminates 50 MB install for users not running local embeddings
|
|
15
|
+
- PRIVACY.md added to published package
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Removed broken category drill-down that showed "Other (3137)" for uncategorized adapters
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1.0.1 — 2026-04-02
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Setup wizard replaced multi-level category drill-down with credential auto-wire — scans `~/.epic-ai/.env` and MCP client configs to pre-select adapters whose credentials are already present
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
10
29
|
## 1.0.0 — 2026-04-02
|
|
11
30
|
|
|
12
31
|
### Added
|
package/dist/bin/setup.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Epic AI® Legion — CLI Entry Point
|
|
4
|
-
* `npx @epicai/legion`
|
|
5
|
-
* `
|
|
6
|
-
* `
|
|
7
|
-
* `
|
|
8
|
-
* `
|
|
9
|
-
* `
|
|
4
|
+
* `legion` / `npx @epicai/legion` — setup wizard
|
|
5
|
+
* `legion serve` / `--serve` — MCP server mode
|
|
6
|
+
* `legion add <name>` — add adapter and enter credentials
|
|
7
|
+
* `legion remove <name>` — remove an adapter
|
|
8
|
+
* `legion health` — check adapter status
|
|
9
|
+
* `legion list` — show Curated + Custom adapters
|
|
10
|
+
* `legion search [term]` — search all available adapters
|
|
11
|
+
* `legion configure` — connect credentials and wire adapters
|
|
12
|
+
* `legion help` — show all commands
|
|
10
13
|
*
|
|
11
14
|
* Legion is an Intelligent Virtual Assistant (IVA) — the AI classifies intent,
|
|
12
15
|
* selects adapters, calls them, and synthesizes a response through your local
|
package/dist/bin/setup.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Epic AI® Legion — CLI Entry Point
|
|
4
|
-
* `npx @epicai/legion`
|
|
5
|
-
* `
|
|
6
|
-
* `
|
|
7
|
-
* `
|
|
8
|
-
* `
|
|
9
|
-
* `
|
|
4
|
+
* `legion` / `npx @epicai/legion` — setup wizard
|
|
5
|
+
* `legion serve` / `--serve` — MCP server mode
|
|
6
|
+
* `legion add <name>` — add adapter and enter credentials
|
|
7
|
+
* `legion remove <name>` — remove an adapter
|
|
8
|
+
* `legion health` — check adapter status
|
|
9
|
+
* `legion list` — show Curated + Custom adapters
|
|
10
|
+
* `legion search [term]` — search all available adapters
|
|
11
|
+
* `legion configure` — connect credentials and wire adapters
|
|
12
|
+
* `legion help` — show all commands
|
|
10
13
|
*
|
|
11
14
|
* Legion is an Intelligent Virtual Assistant (IVA) — the AI classifies intent,
|
|
12
15
|
* selects adapters, calls them, and synthesizes a response through your local
|
|
@@ -749,39 +752,244 @@ async function cmdHealth() {
|
|
|
749
752
|
state.lastHealthCheck = new Date().toISOString();
|
|
750
753
|
saveState(state);
|
|
751
754
|
}
|
|
752
|
-
|
|
755
|
+
// ─── Curated adapter IDs (mirrored here for list/search — keep in sync with wizard) ──
|
|
756
|
+
const CURATED_IDS = [
|
|
757
|
+
'com-claude-mcp-pubmed-pubmed',
|
|
758
|
+
'govbase-mcp',
|
|
759
|
+
'searchcode',
|
|
760
|
+
'robtex',
|
|
761
|
+
];
|
|
762
|
+
// ─── legion list ────────────────────────────────────────────
|
|
763
|
+
async function cmdList() {
|
|
753
764
|
const pc = (await import('picocolors')).default;
|
|
754
765
|
const all = await loadAllAdapters();
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
//
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
766
|
+
const state = loadState();
|
|
767
|
+
// Curated tier
|
|
768
|
+
const curatedRows = CURATED_IDS.map(id => all.find(a => a.id === id)).filter(Boolean);
|
|
769
|
+
// Custom tier — in state but not curated
|
|
770
|
+
const customIds = Object.keys(state.adapters).filter(id => !CURATED_IDS.includes(id));
|
|
771
|
+
const customRows = customIds.map(id => all.find(a => a.id === id) || { id, name: id, type: 'unknown' });
|
|
772
|
+
console.log('');
|
|
773
|
+
// Curated
|
|
774
|
+
console.log(` ${pc.bold('Curated')} ${pc.dim(`(${curatedRows.length})`)} ${pc.dim('— open data, no credentials required')}`);
|
|
775
|
+
console.log('');
|
|
776
|
+
for (const a of curatedRows) {
|
|
777
|
+
const toolCount = a.rest?.toolCount || a.mcp?.toolCount || 0;
|
|
778
|
+
const typeLabel = a.type === 'mcp' ? pc.dim('MCP') : a.type === 'both' ? pc.dim('REST+MCP') : pc.dim('REST');
|
|
779
|
+
console.log(` ${pc.cyan(a.id.padEnd(35))} ${typeLabel} ${String(toolCount).padStart(3)} tools ${pc.dim((a.description || '').slice(0, 50))}`);
|
|
769
780
|
}
|
|
770
781
|
console.log('');
|
|
771
|
-
|
|
782
|
+
// Custom
|
|
783
|
+
console.log(` ${pc.bold('Custom')} ${pc.dim(`(${customRows.length})`)} ${pc.dim('— your APIs and credentials')}`);
|
|
772
784
|
console.log('');
|
|
773
|
-
|
|
774
|
-
console.log(`
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
785
|
+
if (customRows.length === 0) {
|
|
786
|
+
console.log(` ${pc.dim('None yet — run:')} ${pc.cyan('legion configure')}`);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
for (const a of customRows) {
|
|
790
|
+
const toolCount = a.rest?.toolCount || 0;
|
|
791
|
+
const typeLabel = a.type === 'mcp' ? pc.dim('MCP') : a.type === 'both' ? pc.dim('REST+MCP') : pc.dim('REST');
|
|
792
|
+
console.log(` ${pc.cyan(a.id.padEnd(35))} ${typeLabel} ${String(toolCount).padStart(3)} tools ${pc.dim((a.description || '').slice(0, 50))}`);
|
|
779
793
|
}
|
|
780
|
-
|
|
781
|
-
|
|
794
|
+
}
|
|
795
|
+
console.log('');
|
|
796
|
+
}
|
|
797
|
+
// ─── legion search ──────────────────────────────────────────
|
|
798
|
+
async function cmdSearch(term) {
|
|
799
|
+
const pc = (await import('picocolors')).default;
|
|
800
|
+
const all = await loadAllAdapters();
|
|
801
|
+
const state = loadState();
|
|
802
|
+
if (!term) {
|
|
803
|
+
// No term — show curated tier + hint
|
|
804
|
+
const curatedRows = CURATED_IDS.map(id => all.find(a => a.id === id)).filter(Boolean);
|
|
805
|
+
console.log('');
|
|
806
|
+
console.log(` ${pc.bold('Curated adapters')} ${pc.dim('— vetted, open data, no credentials required')}`);
|
|
807
|
+
console.log('');
|
|
808
|
+
for (const a of curatedRows) {
|
|
809
|
+
const toolCount = a.rest?.toolCount || a.mcp?.toolCount || 0;
|
|
810
|
+
console.log(` ${pc.cyan(a.id.padEnd(35))} ${String(toolCount).padStart(3)} tools ${pc.dim((a.description || '').slice(0, 60))}`);
|
|
782
811
|
}
|
|
783
812
|
console.log('');
|
|
813
|
+
console.log(` ${pc.dim(`Search all ${all.length} available adapters:`)} ${pc.cyan('legion search <term>')}`);
|
|
814
|
+
console.log('');
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const t = term.toLowerCase();
|
|
818
|
+
const results = all.filter(a => a.id.includes(t) ||
|
|
819
|
+
a.name.toLowerCase().includes(t) ||
|
|
820
|
+
(a.description || '').toLowerCase().includes(t) ||
|
|
821
|
+
(a.category || '').includes(t));
|
|
822
|
+
if (results.length === 0) {
|
|
823
|
+
console.log(`\n No adapters matched "${term}". Try a broader term.\n`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
// Sort: curated first, then custom (in state), then rest
|
|
827
|
+
const customInState = new Set(Object.keys(state.adapters));
|
|
828
|
+
const sorted = [
|
|
829
|
+
...results.filter(a => CURATED_IDS.includes(a.id)),
|
|
830
|
+
...results.filter(a => !CURATED_IDS.includes(a.id) && customInState.has(a.id)),
|
|
831
|
+
...results.filter(a => !CURATED_IDS.includes(a.id) && !customInState.has(a.id)),
|
|
832
|
+
];
|
|
833
|
+
const shown = sorted.slice(0, 20);
|
|
834
|
+
console.log('');
|
|
835
|
+
console.log(` ${pc.bold(`${results.length} adapters`)} matching "${term}"${results.length > 20 ? pc.dim(' (showing top 20)') : ''}`);
|
|
836
|
+
console.log('');
|
|
837
|
+
for (const a of shown) {
|
|
838
|
+
const toolCount = a.rest?.toolCount || a.mcp?.toolCount || 0;
|
|
839
|
+
const tag = CURATED_IDS.includes(a.id)
|
|
840
|
+
? pc.green('curated')
|
|
841
|
+
: customInState.has(a.id)
|
|
842
|
+
? pc.cyan('configured')
|
|
843
|
+
: pc.dim('available');
|
|
844
|
+
console.log(` ${pc.cyan(a.id.padEnd(35))} ${String(toolCount).padStart(3)} tools [${tag}]`);
|
|
845
|
+
if (a.description)
|
|
846
|
+
console.log(` ${pc.dim((' ').repeat(35))} ${pc.dim(a.description.slice(0, 70))}`);
|
|
847
|
+
console.log('');
|
|
848
|
+
}
|
|
849
|
+
const unconfigured = shown.filter(a => !CURATED_IDS.includes(a.id) && !customInState.has(a.id));
|
|
850
|
+
if (unconfigured.length > 0) {
|
|
851
|
+
console.log(` ${pc.dim('Add one:')} ${pc.cyan(`legion add ${unconfigured[0].id}`)}`);
|
|
852
|
+
console.log('');
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// ─── legion configure ───────────────────────────────────────
|
|
856
|
+
async function cmdConfigure() {
|
|
857
|
+
const p = await import('@clack/prompts');
|
|
858
|
+
const pc = (await import('picocolors')).default;
|
|
859
|
+
console.log('');
|
|
860
|
+
p.intro(pc.bgCyan(pc.black(' Legion Configure — Connect Your APIs ')));
|
|
861
|
+
const all = await loadAllAdapters();
|
|
862
|
+
// Step 1: Where to look for credentials
|
|
863
|
+
const scanTargets = await p.multiselect({
|
|
864
|
+
message: 'Where should Legion look for existing credentials?',
|
|
865
|
+
options: [
|
|
866
|
+
{ value: 'epic-ai', label: '~/.epic-ai/.env', hint: 'Legion\'s credential store' },
|
|
867
|
+
{ value: 'home', label: '~/.env', hint: 'home directory env file' },
|
|
868
|
+
{ value: 'cwd', label: '.env in current directory', hint: `${process.cwd()}/.env` },
|
|
869
|
+
],
|
|
870
|
+
initialValues: ['epic-ai'],
|
|
871
|
+
required: true,
|
|
872
|
+
});
|
|
873
|
+
if (p.isCancel(scanTargets)) {
|
|
874
|
+
p.cancel('Cancelled.');
|
|
875
|
+
process.exit(0);
|
|
876
|
+
}
|
|
877
|
+
// Step 2: Scan and match
|
|
878
|
+
const s = p.spinner();
|
|
879
|
+
s.start('Scanning for credentials');
|
|
880
|
+
const foundCreds = {};
|
|
881
|
+
if (scanTargets.includes('epic-ai')) {
|
|
882
|
+
Object.assign(foundCreds, loadCredentials());
|
|
883
|
+
}
|
|
884
|
+
if (scanTargets.includes('home')) {
|
|
885
|
+
const p2 = join(homedir(), '.env');
|
|
886
|
+
if (existsSync(p2)) {
|
|
887
|
+
const lines = readFileSync(p2, 'utf-8').split('\n');
|
|
888
|
+
for (const line of lines) {
|
|
889
|
+
const eq = line.indexOf('=');
|
|
890
|
+
if (eq > 0)
|
|
891
|
+
foundCreds[line.slice(0, eq)] = line.slice(eq + 1);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (scanTargets.includes('cwd')) {
|
|
896
|
+
const p3 = join(process.cwd(), '.env');
|
|
897
|
+
if (existsSync(p3)) {
|
|
898
|
+
const lines = readFileSync(p3, 'utf-8').split('\n');
|
|
899
|
+
for (const line of lines) {
|
|
900
|
+
const eq = line.indexOf('=');
|
|
901
|
+
if (eq > 0)
|
|
902
|
+
foundCreds[line.slice(0, eq)] = line.slice(eq + 1);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
s.stop('Scan complete');
|
|
907
|
+
// Match credentials to adapters
|
|
908
|
+
const matched = [];
|
|
909
|
+
for (const adapter of all) {
|
|
910
|
+
if (CURATED_IDS.includes(adapter.id))
|
|
911
|
+
continue; // skip curated — already configured
|
|
912
|
+
const envKey = adapter.rest?.envKey;
|
|
913
|
+
if (envKey && foundCreds[envKey]) {
|
|
914
|
+
matched.push({ adapter, key: envKey });
|
|
915
|
+
}
|
|
916
|
+
else if (adapter.mcp?.envKeys) {
|
|
917
|
+
for (const k of adapter.mcp.envKeys) {
|
|
918
|
+
if (foundCreds[k]) {
|
|
919
|
+
matched.push({ adapter, key: k });
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (matched.length === 0) {
|
|
926
|
+
p.log.info('No matching credentials found in scanned locations.');
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
p.note(matched.map(m => ` ${pc.green(m.key.padEnd(30))} → ${pc.cyan(m.adapter.name)}`).join('\n'), `Found ${matched.length} credential${matched.length !== 1 ? 's' : ''}`);
|
|
930
|
+
// Step 3: Confirm which to wire
|
|
931
|
+
const toWire = await p.multiselect({
|
|
932
|
+
message: 'Wire these adapters?',
|
|
933
|
+
options: matched.map(m => ({
|
|
934
|
+
value: m.adapter.id,
|
|
935
|
+
label: m.adapter.name,
|
|
936
|
+
hint: `${m.key} → ${m.adapter.description?.slice(0, 50) || m.adapter.id}`,
|
|
937
|
+
})),
|
|
938
|
+
initialValues: matched.map(m => m.adapter.id),
|
|
939
|
+
required: false,
|
|
940
|
+
});
|
|
941
|
+
if (p.isCancel(toWire)) {
|
|
942
|
+
p.cancel('Cancelled.');
|
|
943
|
+
process.exit(0);
|
|
944
|
+
}
|
|
945
|
+
// Step 4: Write to state and config
|
|
946
|
+
const state = loadState();
|
|
947
|
+
const config = loadConfig() || { selectedAdapters: [], secretsProvider: 'manual', aiClient: 'unknown' };
|
|
948
|
+
for (const id of toWire) {
|
|
949
|
+
const m = matched.find(x => x.adapter.id === id);
|
|
950
|
+
// Copy credential to ~/.epic-ai/.env if it came from elsewhere
|
|
951
|
+
writeCredential(m.key, foundCreds[m.key]);
|
|
952
|
+
state.adapters[id] = { type: m.adapter.type || 'unknown', status: 'configured', toolCount: m.adapter.rest?.toolCount || 0, lastVerified: null };
|
|
953
|
+
if (!config.selectedAdapters.includes(id))
|
|
954
|
+
config.selectedAdapters.push(id);
|
|
955
|
+
}
|
|
956
|
+
saveState(state);
|
|
957
|
+
saveConfig(config);
|
|
958
|
+
if (toWire.length > 0) {
|
|
959
|
+
p.log.success(`${toWire.length} adapter${toWire.length !== 1 ? 's' : ''} configured.`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
// Step 5: Add more manually?
|
|
963
|
+
const addMore = await p.confirm({ message: 'Add adapters manually?', initialValue: false });
|
|
964
|
+
if (!p.isCancel(addMore) && addMore) {
|
|
965
|
+
const name = await p.text({ message: 'Adapter ID (run "legion search <term>" to find one):' });
|
|
966
|
+
if (!p.isCancel(name) && name) {
|
|
967
|
+
await cmdAdd(name);
|
|
968
|
+
}
|
|
784
969
|
}
|
|
970
|
+
p.outro(`${pc.green('Done.')} Run ${pc.cyan('legion list')} to see your configured adapters.`);
|
|
971
|
+
}
|
|
972
|
+
// ─── legion help ────────────────────────────────────────────
|
|
973
|
+
async function cmdHelp() {
|
|
974
|
+
const pc = (await import('picocolors')).default;
|
|
975
|
+
console.log('');
|
|
976
|
+
console.log(` ${pc.bold('Epic AI® Legion')} — Intelligent Virtual Assistant (IVA)`);
|
|
977
|
+
console.log('');
|
|
978
|
+
console.log(` ${pc.bold('Commands:')}`);
|
|
979
|
+
console.log('');
|
|
980
|
+
console.log(` ${pc.cyan('legion')} run the setup wizard`);
|
|
981
|
+
console.log(` ${pc.cyan('legion query "<question>"')} route a question to your adapters via your AI client`);
|
|
982
|
+
console.log(` ${pc.cyan('legion list')} show Curated + Custom adapters`);
|
|
983
|
+
console.log(` ${pc.cyan('legion search [term]')} search all available adapters`);
|
|
984
|
+
console.log(` ${pc.cyan('legion add <id>')} add an adapter and enter credentials`);
|
|
985
|
+
console.log(` ${pc.cyan('legion remove <id>')} remove an adapter`);
|
|
986
|
+
console.log(` ${pc.cyan('legion configure')} connect your APIs and credentials`);
|
|
987
|
+
console.log(` ${pc.cyan('legion health')} check adapter status`);
|
|
988
|
+
console.log(` ${pc.cyan('legion serve')} start as MCP server (used by AI clients)`);
|
|
989
|
+
console.log(` ${pc.cyan('legion help')} show this help`);
|
|
990
|
+
console.log('');
|
|
991
|
+
console.log(` ${pc.dim('Docs:')} https://legion.epic-ai.io`);
|
|
992
|
+
console.log('');
|
|
785
993
|
}
|
|
786
994
|
// ─── Setup Wizard ───────────────────────────────────────────
|
|
787
995
|
async function runSetupWizard() {
|
|
@@ -918,59 +1126,107 @@ async function runSetupWizard() {
|
|
|
918
1126
|
p.log.success(`Using ${system.localBackend} on port ${system.localPort}`);
|
|
919
1127
|
configuredClients.push('local');
|
|
920
1128
|
}
|
|
921
|
-
// Step 3:
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
{
|
|
926
|
-
|
|
1129
|
+
// Step 3: Auto-configure all curated (vetted zero-credential) adapters
|
|
1130
|
+
// IMPORTANT: Only add adapters to CURATED after manual vetting — confirm they
|
|
1131
|
+
// return real data, contain no adult/inappropriate content, and are stable.
|
|
1132
|
+
const CURATED = [
|
|
1133
|
+
{
|
|
1134
|
+
id: 'com-claude-mcp-pubmed-pubmed',
|
|
1135
|
+
name: 'PubMed',
|
|
1136
|
+
desc: 'Search 36 million biomedical research papers',
|
|
1137
|
+
tools: 7,
|
|
1138
|
+
demoQuery: 'Recent clinical trials on GLP-1 drugs for obesity',
|
|
1139
|
+
exampleQuery: 'legion query "recent clinical trials on GLP-1 drugs for obesity"',
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
id: 'govbase-mcp',
|
|
1143
|
+
name: 'Govbase',
|
|
1144
|
+
desc: 'Government data — legislators, bills, committees',
|
|
1145
|
+
tools: 10,
|
|
1146
|
+
demoQuery: 'Who chairs the Senate Armed Services Committee?',
|
|
1147
|
+
exampleQuery: 'legion query "who chairs the Senate Armed Services Committee?"',
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
id: 'searchcode',
|
|
1151
|
+
name: 'Searchcode',
|
|
1152
|
+
desc: 'Search 75 billion lines of open source code',
|
|
1153
|
+
tools: 6,
|
|
1154
|
+
demoQuery: 'Open source implementations of rate limiting in Go',
|
|
1155
|
+
exampleQuery: 'legion query "open source implementations of rate limiting in Go"',
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
id: 'robtex',
|
|
1159
|
+
name: 'Robtex',
|
|
1160
|
+
desc: 'Network intelligence — DNS, IP, ASN lookups',
|
|
1161
|
+
tools: 45,
|
|
1162
|
+
demoQuery: 'DNS records and ASN for cloudflare.com',
|
|
1163
|
+
exampleQuery: 'legion query "DNS records and ASN for cloudflare.com"',
|
|
1164
|
+
},
|
|
927
1165
|
];
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
options: [
|
|
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>' },
|
|
937
|
-
],
|
|
938
|
-
});
|
|
939
|
-
if (p.isCancel(demoChoice)) {
|
|
940
|
-
p.cancel('Setup cancelled.');
|
|
941
|
-
process.exit(0);
|
|
942
|
-
}
|
|
943
|
-
const selectedAdapterIds = [];
|
|
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`);
|
|
948
|
-
}
|
|
949
|
-
// Step 4: Save state and config
|
|
1166
|
+
const s2 = p.spinner();
|
|
1167
|
+
s2.start('Connecting curated data sources');
|
|
950
1168
|
const state = loadState();
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1169
|
+
const curatedAdapterEntries = CURATED.map(c => allAdapters.find(a => a.id === c.id)).filter(Boolean);
|
|
1170
|
+
for (const c of CURATED) {
|
|
1171
|
+
const adapter = allAdapters.find(a => a.id === c.id);
|
|
1172
|
+
state.adapters[c.id] = {
|
|
1173
|
+
type: adapter?.type || 'mcp',
|
|
955
1174
|
status: 'configured',
|
|
956
|
-
toolCount:
|
|
1175
|
+
toolCount: c.tools,
|
|
957
1176
|
lastVerified: null,
|
|
958
1177
|
};
|
|
959
1178
|
}
|
|
960
1179
|
saveState(state);
|
|
961
1180
|
saveConfig({
|
|
962
|
-
selectedAdapters:
|
|
1181
|
+
selectedAdapters: CURATED.map(c => c.id),
|
|
963
1182
|
secretsProvider: 'manual',
|
|
964
1183
|
aiClient: configuredClients.join(','),
|
|
965
1184
|
localBackend: system.localBackend || undefined,
|
|
966
1185
|
});
|
|
967
|
-
|
|
1186
|
+
s2.stop('Curated data sources connected');
|
|
1187
|
+
p.note(CURATED.map(c => `${pc.green('✓')} ${c.name.padEnd(14)} ${String(c.tools).padStart(2)} tools ${pc.dim(c.desc)}`).join('\n'), `Curated (${CURATED.length}) — no credentials required`);
|
|
1188
|
+
// Step 4: Routing demo — prove intelligence in-process, no network calls
|
|
1189
|
+
const { ToolPreFilter } = await import('../federation/ToolPreFilter.js');
|
|
1190
|
+
function buildDemoTools(adapters) {
|
|
1191
|
+
const tools = [];
|
|
1192
|
+
for (const adapter of adapters) {
|
|
1193
|
+
const toolNames = adapter.rest?.toolNames || adapter.mcp?.toolNames || [];
|
|
1194
|
+
if (toolNames.length === 0) {
|
|
1195
|
+
tools.push({ name: `${adapter.id}:default`, description: `${adapter.name} — ${adapter.description || adapter.id}`, parameters: { type: 'object', properties: {} }, server: adapter.id, tier: 'orchestrated' });
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
for (const t of toolNames) {
|
|
1199
|
+
tools.push({ name: `${adapter.id}:${t}`, description: `${adapter.name} — ${t.replace(/_/g, ' ')}`, parameters: { type: 'object', properties: {} }, server: adapter.id, tier: 'orchestrated' });
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return tools;
|
|
1204
|
+
}
|
|
1205
|
+
const demoFilter = new ToolPreFilter();
|
|
1206
|
+
demoFilter.index(buildDemoTools(curatedAdapterEntries));
|
|
1207
|
+
const routingLines = [];
|
|
1208
|
+
for (const c of CURATED) {
|
|
1209
|
+
const matches = await demoFilter.select(c.demoQuery, { maxTools: 3, maxPerServer: 2 });
|
|
1210
|
+
const topId = matches[0]?.server;
|
|
1211
|
+
const routed = topId === c.id;
|
|
1212
|
+
const arrow = routed ? pc.green('→') : pc.yellow('→');
|
|
1213
|
+
const adapterLabel = routed ? pc.green(c.name) : pc.yellow(topId || '?');
|
|
1214
|
+
routingLines.push(` ${pc.dim(`"${c.demoQuery.slice(0, 48)}${c.demoQuery.length > 48 ? '…' : ''}"`)}`);
|
|
1215
|
+
routingLines.push(` ${arrow} ${adapterLabel}`);
|
|
1216
|
+
routingLines.push('');
|
|
1217
|
+
}
|
|
1218
|
+
p.note(routingLines.join('\n').trimEnd(), 'Routing intelligence');
|
|
1219
|
+
// Step 5: Outro — hand off to shell
|
|
1220
|
+
p.note([
|
|
1221
|
+
pc.bold('Try these yourself:'),
|
|
1222
|
+
'',
|
|
1223
|
+
...CURATED.map(c => ` ${pc.cyan(c.exampleQuery)}`),
|
|
1224
|
+
].join('\n'), 'Test it');
|
|
968
1225
|
p.note([
|
|
969
|
-
`
|
|
970
|
-
`
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
p.outro(`${pc.green('Legion is ready.')} Your credentials never leave this machine.`);
|
|
1226
|
+
` ${pc.cyan('legion configure')} connect your APIs and credentials`,
|
|
1227
|
+
` ${pc.cyan('legion help')} see all commands`,
|
|
1228
|
+
].join('\n'), 'When you\'re ready to connect your own APIs');
|
|
1229
|
+
p.outro(`${pc.green('Legion is ready.')} Your data never leaves this machine.`);
|
|
974
1230
|
}
|
|
975
1231
|
// ─── Main router ────────────────────────────────────────────
|
|
976
1232
|
async function main() {
|
|
@@ -983,14 +1239,14 @@ async function main() {
|
|
|
983
1239
|
switch (command) {
|
|
984
1240
|
case 'add':
|
|
985
1241
|
if (!args[1]) {
|
|
986
|
-
console.error('Usage:
|
|
1242
|
+
console.error('Usage: legion add <adapter-id> (run "legion search <term>" to find one)');
|
|
987
1243
|
process.exit(1);
|
|
988
1244
|
}
|
|
989
1245
|
await cmdAdd(args[1]);
|
|
990
1246
|
break;
|
|
991
1247
|
case 'remove':
|
|
992
1248
|
if (!args[1]) {
|
|
993
|
-
console.error('Usage:
|
|
1249
|
+
console.error('Usage: legion remove <adapter-id>');
|
|
994
1250
|
process.exit(1);
|
|
995
1251
|
}
|
|
996
1252
|
await cmdRemove(args[1]);
|
|
@@ -999,7 +1255,24 @@ async function main() {
|
|
|
999
1255
|
await cmdHealth();
|
|
1000
1256
|
break;
|
|
1001
1257
|
case 'list':
|
|
1002
|
-
await cmdList(
|
|
1258
|
+
await cmdList();
|
|
1259
|
+
break;
|
|
1260
|
+
case 'search':
|
|
1261
|
+
await cmdSearch(args[1]);
|
|
1262
|
+
break;
|
|
1263
|
+
case 'configure':
|
|
1264
|
+
await cmdConfigure();
|
|
1265
|
+
break;
|
|
1266
|
+
case 'help':
|
|
1267
|
+
case '--help':
|
|
1268
|
+
case '-h':
|
|
1269
|
+
await cmdHelp();
|
|
1270
|
+
break;
|
|
1271
|
+
case 'query':
|
|
1272
|
+
console.log('\n legion query routes through your AI client (Claude Code, Cursor, etc.)');
|
|
1273
|
+
console.log(' Start Legion as an MCP server: legion serve');
|
|
1274
|
+
console.log(' Then ask your AI client: "use legion_query to find..."');
|
|
1275
|
+
console.log('\n Run: legion help\n');
|
|
1003
1276
|
break;
|
|
1004
1277
|
case 'serve':
|
|
1005
1278
|
await startMcpServer();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicai/legion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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",
|
|
@@ -121,6 +121,9 @@
|
|
|
121
121
|
"vitest": "^3.0.0"
|
|
122
122
|
},
|
|
123
123
|
"scripts": {
|
|
124
|
+
"release:patch": "npm run build && npm version patch && npm publish --access public",
|
|
125
|
+
"release:minor": "npm run build && npm version minor && npm publish --access public",
|
|
126
|
+
"release:major": "npm run build && npm version major && npm publish --access public",
|
|
124
127
|
"build": "tsc",
|
|
125
128
|
"postbuild": "npx tsx ../epic-ai/src/fabrique/generate-adapter-catalog.ts",
|
|
126
129
|
"pretest": "tsc",
|