@builtbyecho/public-api-finder 0.5.4 → 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 +45 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builtbyecho/public-api-finder",
3
- "version": "0.5.4",
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 = 13;
15
+ const DATA_VERSION = 14;
16
16
 
17
17
  const ENRICHMENT_FIELDS = [
18
18
  'tags',
@@ -574,23 +574,51 @@ 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);
594
622
 
595
623
  export function getCuratedApis() {
596
624
  return ENRICHED_CURATED_APIS.map(api => ({ ...api, tags: [...(api.tags || [])], domains: [...(api.domains || [])], useCases: [...(api.useCases || [])] }));
@@ -705,8 +733,10 @@ function applyQueryHints(args) {
705
733
  if (!args.cors && /\b(cors|frontend-safe|browser-safe|frontend safe|browser safe)\b/.test(query)) args.cors = 'Yes';
706
734
  }
707
735
 
736
+ const SEARCH_STOPWORDS = new Set(['a', 'an', 'and', 'api', 'apis', 'for', 'from', 'in', 'no', 'of', 'on', 'or', 'the', 'to', 'with']);
737
+
708
738
  function tokenSet(text) {
709
- 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)) || []);
710
740
  }
711
741
 
712
742
  function intersectionCount(a, b) {
@@ -943,7 +973,8 @@ async function buildData() {
943
973
  } else sourceStatus['apis-guru'] = `error: ${guru.reason.message}`;
944
974
  sourceStatus.curated = ENRICHED_CURATED_APIS.length;
945
975
  entries.push(...ENRICHED_CURATED_APIS);
946
- 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 };
947
978
  }
948
979
 
949
980
  function keyFor(entry) {
@@ -1006,7 +1037,7 @@ function dedupe(entries) {
1006
1037
  async function loadData(refresh = false) {
1007
1038
  if (!refresh && await cacheIsFresh()) {
1008
1039
  const cached = JSON.parse(await readFile(CACHE_PATH, 'utf8'));
1009
- if (cached.dataVersion === DATA_VERSION) return cached.entries || [];
1040
+ if (cached.dataVersion === DATA_VERSION) return (cached.entries || []).map(enrichApiMetadata);
1010
1041
  }
1011
1042
  const data = await buildData();
1012
1043
  await mkdir(dirname(CACHE_PATH), { recursive: true });
@@ -1084,6 +1115,10 @@ async function checkRows(rows) {
1084
1115
  return checked;
1085
1116
  }
1086
1117
 
1118
+ export async function buildSearchIndex(options = {}) {
1119
+ return loadData(Boolean(options.refresh));
1120
+ }
1121
+
1087
1122
  export async function searchApis(options = {}) {
1088
1123
  const args = {
1089
1124
  query: options.query || '',