@builtbyecho/public-api-finder 0.5.3 → 0.5.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +49 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builtbyecho/public-api-finder",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Find free/public APIs for agents and prototypes.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -12,7 +12,7 @@ const SOURCES = {
12
12
  };
13
13
  const CACHE_PATH = process.env.PUBLIC_API_FINDER_CACHE || join(homedir(), '.cache', 'public-api-finder', 'all.json');
14
14
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
15
- const DATA_VERSION = 12;
15
+ const DATA_VERSION = 14;
16
16
 
17
17
  const ENRICHMENT_FIELDS = [
18
18
  'tags',
@@ -574,23 +574,55 @@ function normalizeAuthType(auth) {
574
574
  return value && value !== 'unknown' ? value : 'unknown';
575
575
  }
576
576
 
577
- function enrichCuratedApi(api) {
577
+ function wordsFrom(value) {
578
+ return String(value || '')
579
+ .replace(/&/g, ' and ')
580
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
581
+ .toLowerCase()
582
+ .match(/[a-z0-9]+/g) || [];
583
+ }
584
+
585
+ function categoryTags(category) {
586
+ const words = wordsFrom(category).filter(word => !['and', 'api', 'apis', 'openapi', 'unknown'].includes(word));
587
+ const tags = [];
588
+ const categoryText = String(category || '').toLowerCase();
589
+ if (categoryText && !['unknown', 'openapi'].includes(categoryText)) tags.push(categoryText.replace(/\s+/g, ' '));
590
+ for (const word of words) tags.push(word);
591
+ return tags;
592
+ }
593
+
594
+ function inferTags(api, specific = {}) {
595
+ const text = `${api.name || ''} ${api.category || ''} ${api.description || ''} ${api.url || ''} ${api.provider || ''} ${(specific.tags || []).join(' ')}`;
596
+ return INTENT_TAG_RULES.filter(([pattern]) => pattern.test(text)).map(([, tag]) => tag);
597
+ }
598
+
599
+ function enrichApiMetadata(api) {
578
600
  const categoryBase = CURATED_CATEGORY_ENRICHMENTS[api.category] || {};
579
601
  const specific = CURATED_API_ENRICHMENTS[api.name] || {};
580
- const text = `${api.name || ''} ${api.description || ''} ${(specific.tags || []).join(' ')}`;
581
- const inferredTags = INTENT_TAG_RULES.filter(([pattern]) => pattern.test(text)).map(([, tag]) => tag);
582
602
  const merged = { ...categoryBase, ...specific, ...api };
603
+ const tags = uniqueStrings([
604
+ ...(categoryBase.tags || []),
605
+ ...(specific.tags || []),
606
+ ...inferTags(api, specific),
607
+ ...(api.tags || []),
608
+ ...categoryTags(api.category),
609
+ ]);
610
+
583
611
  merged.authType = api.authType || specific.authType || categoryBase.authType || normalizeAuthType(api.auth);
584
612
  merged.providerType = api.providerType || specific.providerType || categoryBase.providerType || 'public-api';
585
- merged.tags = uniqueStrings([...(categoryBase.tags || []), ...(specific.tags || []), ...inferredTags, ...(api.tags || [])]);
586
- merged.domains = uniqueStrings([...(categoryBase.domains || []), ...(specific.domains || []), ...(api.domains || [])]);
613
+ merged.tags = tags.length ? tags : ['public api'];
614
+ merged.domains = uniqueStrings([...(categoryBase.domains || []), ...(specific.domains || []), ...(api.domains || []), ...categoryTags(api.category).slice(0, 2)]);
587
615
  merged.useCases = uniqueStrings([...(categoryBase.useCases || []), ...(specific.useCases || []), ...(api.useCases || [])]);
588
616
  merged.caveats = uniqueStrings([...(categoryBase.caveats || []), ...(specific.caveats || []), ...(api.caveats || [])]);
589
617
  merged.bestFor = api.bestFor || specific.bestFor || categoryBase.bestFor;
590
618
  return merged;
591
619
  }
592
620
 
593
- const ENRICHED_CURATED_APIS = CURATED_APIS.map(enrichCuratedApi);
621
+ const ENRICHED_CURATED_APIS = CURATED_APIS.map(enrichApiMetadata);
622
+
623
+ export function getCuratedApis() {
624
+ return ENRICHED_CURATED_APIS.map(api => ({ ...api, tags: [...(api.tags || [])], domains: [...(api.domains || [])], useCases: [...(api.useCases || [])] }));
625
+ }
594
626
 
595
627
  function compactName(value) { return String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, ''); }
596
628
  const KNOWN_BEST_NAMES = new Map(ENRICHED_CURATED_APIS.map(api => [compactName(api.name), 15]));
@@ -701,8 +733,10 @@ function applyQueryHints(args) {
701
733
  if (!args.cors && /\b(cors|frontend-safe|browser-safe|frontend safe|browser safe)\b/.test(query)) args.cors = 'Yes';
702
734
  }
703
735
 
736
+ const SEARCH_STOPWORDS = new Set(['a', 'an', 'and', 'api', 'apis', 'for', 'from', 'in', 'no', 'of', 'on', 'or', 'the', 'to', 'with']);
737
+
704
738
  function tokenSet(text) {
705
- return new Set(String(text).toLowerCase().match(/[a-z0-9]+/g)?.filter(t => t.length > 1) || []);
739
+ return new Set(String(text).toLowerCase().match(/[a-z0-9]+/g)?.filter(t => t.length > 1 && !SEARCH_STOPWORDS.has(t)) || []);
706
740
  }
707
741
 
708
742
  function intersectionCount(a, b) {
@@ -939,7 +973,8 @@ async function buildData() {
939
973
  } else sourceStatus['apis-guru'] = `error: ${guru.reason.message}`;
940
974
  sourceStatus.curated = ENRICHED_CURATED_APIS.length;
941
975
  entries.push(...ENRICHED_CURATED_APIS);
942
- return { dataVersion: DATA_VERSION, generatedAt: new Date().toISOString(), sourceStatus, entries: dedupe(entries) };
976
+ const deduped = dedupe(entries).map(enrichApiMetadata);
977
+ return { dataVersion: DATA_VERSION, generatedAt: new Date().toISOString(), sourceStatus, entries: deduped };
943
978
  }
944
979
 
945
980
  function keyFor(entry) {
@@ -1002,7 +1037,7 @@ function dedupe(entries) {
1002
1037
  async function loadData(refresh = false) {
1003
1038
  if (!refresh && await cacheIsFresh()) {
1004
1039
  const cached = JSON.parse(await readFile(CACHE_PATH, 'utf8'));
1005
- if (cached.dataVersion === DATA_VERSION) return cached.entries || [];
1040
+ if (cached.dataVersion === DATA_VERSION) return (cached.entries || []).map(enrichApiMetadata);
1006
1041
  }
1007
1042
  const data = await buildData();
1008
1043
  await mkdir(dirname(CACHE_PATH), { recursive: true });
@@ -1080,6 +1115,10 @@ async function checkRows(rows) {
1080
1115
  return checked;
1081
1116
  }
1082
1117
 
1118
+ export async function buildSearchIndex(options = {}) {
1119
+ return loadData(Boolean(options.refresh));
1120
+ }
1121
+
1083
1122
  export async function searchApis(options = {}) {
1084
1123
  const args = {
1085
1124
  query: options.query || '',