@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 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
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Epic AI® Legion — CLI Entry Point
4
- * `npx @epicai/legion` — setup wizard
5
- * `npx @epicai/legion --serve` — MCP server mode
6
- * `npx @epicai/legion add <name>` — add adapter
7
- * `npx @epicai/legion remove <name>` — remove adapter
8
- * `npx @epicai/legion health` — check adapter health
9
- * `npx @epicai/legion list` list all adapters
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` — setup wizard
5
- * `npx @epicai/legion --serve` — MCP server mode
6
- * `npx @epicai/legion add <name>` — add adapter
7
- * `npx @epicai/legion remove <name>` — remove adapter
8
- * `npx @epicai/legion health` — check adapter health
9
- * `npx @epicai/legion list` list all adapters
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
- async function cmdList(searchTerm) {
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
- let filtered = all;
756
- if (searchTerm) {
757
- const term = searchTerm.toLowerCase();
758
- filtered = all.filter(a => a.id.includes(term) || a.name.toLowerCase().includes(term) ||
759
- (a.description || '').toLowerCase().includes(term) ||
760
- (a.category || '').includes(term));
761
- }
762
- // Group by category
763
- const categories = new Map();
764
- for (const a of filtered) {
765
- const cat = a.category || 'other';
766
- if (!categories.has(cat))
767
- categories.set(cat, []);
768
- categories.get(cat).push(a);
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
- console.log(` Epic AI® Legion — ${filtered.length} adapters${searchTerm ? ` matching "${searchTerm}"` : ''}`);
782
+ // Custom
783
+ console.log(` ${pc.bold('Custom')} ${pc.dim(`(${customRows.length})`)} ${pc.dim('— your APIs and credentials')}`);
772
784
  console.log('');
773
- for (const [cat, adapters] of Array.from(categories.entries()).sort((a, b) => b[1].length - a[1].length)) {
774
- console.log(` ${pc.cyan(cat)} (${adapters.length})`);
775
- for (const a of adapters.slice(0, 10)) {
776
- const tools = a.rest?.toolCount ? `${a.rest.toolCount} tools` : '';
777
- const type = a.type === 'mcp' ? pc.dim('MCP') : a.type === 'both' ? pc.dim('REST+MCP') : pc.dim('REST');
778
- console.log(` ${a.id.padEnd(35)} ${type} ${tools}`);
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
- if (adapters.length > 10) {
781
- console.log(` ${pc.dim(`... and ${adapters.length - 10} more`)}`);
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: 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 },
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 demoChoice = await p.select({
929
- message: 'Try a live data connection? These require no credentials:',
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
- for (const id of selectedAdapterIds) {
952
- const adapter = allAdapters.find(a => a.id === id);
953
- state.adapters[id] = {
954
- type: adapter?.type || 'unknown',
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: adapter?.rest?.toolCount || 0,
1175
+ toolCount: c.tools,
957
1176
  lastVerified: null,
958
1177
  };
959
1178
  }
960
1179
  saveState(state);
961
1180
  saveConfig({
962
- selectedAdapters: selectedAdapterIds,
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
- // Outro
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
- `Connect more: ${pc.cyan('npx @epicai/legion add <name>')}`,
970
- `Health check: ${pc.cyan('npx @epicai/legion health')}`,
971
- `List adapters: ${pc.cyan('npx @epicai/legion list')}`,
972
- ].join('\n'), 'Manage your adapters');
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: npx @epicai/legion add <adapter-name>');
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: npx @epicai/legion remove <adapter-name>');
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(args[1]);
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.2",
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",