public-api-finder 0.2.3 → 0.3.1

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Find free/public APIs for agents, prototypes, demos, and integrations.
4
4
 
5
- Powered by multiple sources:
5
+ Powered by multiple sources plus a curated best-known API layer:
6
6
 
7
7
  - [`public-api-lists/public-api-lists`](https://github.com/public-api-lists/public-api-lists) for fast curated JSON discovery
8
8
  - [`public-apis/public-apis`](https://github.com/public-apis/public-apis) for the larger canonical README list
@@ -35,7 +35,7 @@ The skill tells agents to prefer the CLI first, then live-check docs/endpoints b
35
35
 
36
36
  ```text
37
37
  --category <name> Filter by category substring
38
- --source <name> Filter by source: public-api-lists, public-apis, apis-guru
38
+ --source <name> Filter by source: public-api-lists, public-apis, apis-guru, curated
39
39
  --no-auth Only APIs with Auth = No
40
40
  --https Only HTTPS APIs
41
41
  --cors <value> Filter by CORS: Yes, No, Unknown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "public-api-finder",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "Find free/public APIs for agents and prototypes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,7 +5,7 @@ description: Find and evaluate free/public APIs for projects, demos, agents, pro
5
5
 
6
6
  # Public API Finder
7
7
 
8
- Use this skill when a task needs a public API candidate. The CLI searches multiple sources: public-api-lists, public-apis, and APIs.guru OpenAPI directory. Use the CLI first, then live-check docs/endpoints before coding.
8
+ Use this skill when a task needs a public API candidate. The CLI searches multiple sources: public-api-lists, public-apis, APIs.guru OpenAPI directory, and a curated best-known API layer for common domains like crypto, stocks, weather, maps, jobs, sports, media, news, government, and commerce. Use the CLI first, then live-check docs/endpoints before coding.
9
9
 
10
10
  ## Quick command
11
11
 
package/src/cli.js CHANGED
@@ -13,21 +13,116 @@ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
13
13
 
14
14
  const DOMAIN_PROFILES = {
15
15
  crypto: {
16
- triggers: ['crypto', 'cryptocurrency', 'cryptocurrencies', 'bitcoin', 'ethereum', 'blockchain', 'defi', 'token', 'tokens', 'coin', 'coins', 'wallet'],
17
- categories: ['cryptocurrency', 'currency exchange', 'finance', 'financial'],
18
- categoryWeights: { cryptocurrency: 16, 'currency exchange': 5, finance: 3, financial: 3 },
19
- boostTerms: ['crypto', 'cryptocurrency', 'bitcoin', 'ethereum', 'blockchain', 'defi', 'token', 'coin', 'exchange', 'price', 'market'],
20
- weakTerms: ['price', 'prices'],
16
+ triggers: ['crypto', 'cryptocurrency', 'cryptocurrencies', 'bitcoin', 'ethereum', 'solana', 'blockchain', 'defi', 'token', 'tokens', 'coin', 'coins', 'wallet'],
17
+ categoryWeights: { cryptocurrency: 22, 'currency exchange': 3, finance: -4, financial: -4 },
18
+ boostTerms: ['crypto', 'cryptocurrency', 'bitcoin', 'ethereum', 'solana', 'blockchain', 'defi', 'token', 'coin', 'wallet'],
19
+ weakTerms: ['price', 'prices', 'market', 'exchange'],
21
20
  },
22
21
  finance: {
23
- triggers: ['stock', 'stocks', 'equity', 'equities', 'market', 'trading', 'ticker', 'tickers', 'quote', 'quotes', 'etf', 'forex', 'portfolio'],
24
- categories: ['finance', 'financial', 'currency exchange'],
25
- categoryWeights: { finance: 14, financial: 14, 'currency exchange': 5 },
26
- boostTerms: ['stock', 'stocks', 'equity', 'market', 'trading', 'ticker', 'quote', 'quotes', 'forex', 'portfolio'],
22
+ triggers: ['stock', 'stocks', 'equity', 'equities', 'market', 'trading', 'ticker', 'tickers', 'quote', 'quotes', 'etf', 'forex', 'portfolio', 'options'],
23
+ categoryWeights: { finance: 16, financial: 16, 'currency exchange': 5 },
24
+ boostTerms: ['stock', 'stocks', 'equity', 'market', 'trading', 'ticker', 'quote', 'quotes', 'forex', 'portfolio', 'options', 'historical'],
27
25
  weakTerms: ['quote', 'quotes', 'price', 'prices', 'market'],
28
26
  },
27
+ weather: {
28
+ triggers: ['weather', 'forecast', 'radar', 'temperature', 'climate', 'alerts', 'precipitation', 'hourly', 'daily'],
29
+ categoryWeights: { weather: 18, location: 4 },
30
+ boostTerms: ['weather', 'forecast', 'radar', 'temperature', 'climate', 'alerts', 'precipitation', 'meteorological'],
31
+ weakTerms: ['daily', 'hourly'],
32
+ },
33
+ maps: {
34
+ triggers: ['maps', 'map', 'geocoding', 'reverse', 'address', 'coordinates', 'places', 'routing', 'distance', 'timezone', 'location', 'navigation'],
35
+ categoryWeights: { geocoding: 18, location: 12, 'open data': 3 },
36
+ boostTerms: ['map', 'maps', 'geocoding', 'geocoder', 'reverse', 'address', 'coordinates', 'routing', 'places', 'location', 'navigation', 'timezone'],
37
+ weakTerms: ['location'],
38
+ },
39
+ jobs: {
40
+ triggers: ['jobs', 'careers', 'employment', 'hiring', 'salary', 'resume', 'remote', 'companies', 'internships', 'recruitment'],
41
+ categoryWeights: { jobs: 20 },
42
+ boostTerms: ['jobs', 'careers', 'employment', 'hiring', 'salary', 'resume', 'remote', 'recruitment'],
43
+ weakTerms: ['remote'],
44
+ },
45
+ sports: {
46
+ triggers: ['sports', 'scores', 'teams', 'leagues', 'fixtures', 'odds', 'football', 'basketball', 'baseball', 'soccer', 'standings', 'stats'],
47
+ categoryWeights: { 'sports & fitness': 18, entertainment: 4 },
48
+ boostTerms: ['sports', 'scores', 'teams', 'leagues', 'fixtures', 'odds', 'football', 'basketball', 'baseball', 'soccer', 'standings', 'stats'],
49
+ weakTerms: ['scores', 'stats'],
50
+ },
51
+ media: {
52
+ triggers: ['movies', 'movie', 'tv', 'shows', 'streaming', 'actors', 'ratings', 'posters', 'trailers', 'anime', 'imdb', 'films', 'episodes'],
53
+ categoryWeights: { entertainment: 16, anime: 14, media: 12, video: 8 },
54
+ boostTerms: ['movie', 'movies', 'tv', 'show', 'shows', 'streaming', 'actors', 'ratings', 'posters', 'trailers', 'anime', 'imdb', 'film', 'films', 'episodes'],
55
+ weakTerms: ['media'],
56
+ },
57
+ news: {
58
+ triggers: ['news', 'headlines', 'articles', 'breaking', 'media', 'newspapers', 'topics', 'politics', 'world', 'latest'],
59
+ categoryWeights: { news: 20, media: 6 },
60
+ boostTerms: ['news', 'headlines', 'articles', 'breaking', 'newspapers', 'topics', 'politics', 'world', 'latest'],
61
+ weakTerms: ['media', 'search'],
62
+ },
63
+ government: {
64
+ triggers: ['government', 'census', 'legislation', 'representatives', 'elections', 'bills', 'federal', 'agencies', 'public', 'civic'],
65
+ categoryWeights: { government: 18, 'open data': 10 },
66
+ boostTerms: ['government', 'census', 'legislation', 'representatives', 'elections', 'bills', 'federal', 'agencies', 'public data', 'civic'],
67
+ weakTerms: ['public', 'data'],
68
+ },
69
+ commerce: {
70
+ triggers: ['commerce', 'products', 'product', 'prices', 'deals', 'coupons', 'barcode', 'ecommerce', 'shopping', 'reviews', 'inventory', 'catalog', 'store'],
71
+ categoryWeights: { ecommerce: 18, shopping: 16, 'test data': 14, food: 10, 'food & drink': 10, 'open data': 3 },
72
+ boostTerms: ['commerce', 'products', 'product', 'prices', 'deals', 'coupons', 'barcode', 'ecommerce', 'shopping', 'reviews', 'inventory', 'catalog', 'store'],
73
+ weakTerms: ['price', 'prices'],
74
+ },
29
75
  };
30
76
 
77
+ const CURATED_APIS = [
78
+ { name: 'CoinMarketCap', url: 'https://coinmarketcap.com/api/', description: 'Popular cryptocurrency market data, rankings, quotes, metadata, and exchange data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Cryptocurrency', source: 'curated', sourceWeight: 5 },
79
+ { name: 'CoinGecko', url: 'https://www.coingecko.com/en/api', description: 'Popular cryptocurrency prices, market data, tokens, exchanges, and DeFi data.', auth: 'apiKey', https: true, cors: 'Yes', category: 'Cryptocurrency', source: 'curated', sourceWeight: 5 },
80
+
81
+ { name: 'Coinpaprika', url: 'https://api.coinpaprika.com/', description: 'Cryptocurrency prices, coins, market data, exchanges, and historical data.', auth: 'No', https: true, cors: 'Yes', category: 'Cryptocurrency', source: 'curated', sourceWeight: 5 },
82
+ { name: 'CoinCap', url: 'https://docs.coincap.io/', description: 'Real-time cryptocurrency prices, assets, rates, exchanges, and markets.', auth: 'No', https: true, cors: 'Unknown', category: 'Cryptocurrency', source: 'curated', sourceWeight: 5 },
83
+ { name: 'Coinbase', url: 'https://docs.cloud.coinbase.com/', description: 'Coinbase crypto exchange, wallet, price, account, and trading APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Cryptocurrency', source: 'curated', sourceWeight: 5 },
84
+ { name: 'Alpha Vantage', url: 'https://www.alphavantage.co/documentation/', description: 'Stock, ETF, forex, crypto, technical indicators, and market data API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Finance', source: 'curated', sourceWeight: 5 },
85
+ { name: 'Polygon', url: 'https://polygon.io/docs/', description: 'Stock market, options, forex, crypto, tickers, trades, aggregates, and historical market data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Finance', source: 'curated', sourceWeight: 5 },
86
+ { name: 'Twelve Data', url: 'https://twelvedata.com/docs', description: 'Stock, forex, ETF, index, and crypto market data with real-time and historical prices.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Finance', source: 'curated', sourceWeight: 5 },
87
+ { name: 'Tradier', url: 'https://developer.tradier.com/', description: 'US equity, options, quotes, market data, trading, and brokerage API.', auth: 'OAuth', https: true, cors: 'Yes', category: 'Finance', source: 'curated', sourceWeight: 5 },
88
+ { name: 'Finnhub', url: 'https://finnhub.io/docs/api', description: 'Real-time stock, forex, crypto, company fundamentals, news, and alternative market data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Finance', source: 'curated', sourceWeight: 5 },
89
+ { name: 'Open-Meteo', url: 'https://open-meteo.com/en/docs', description: 'Free weather forecast, historical weather, climate, geocoding, and marine weather APIs.', auth: 'No', https: true, cors: 'Yes', category: 'Weather', source: 'curated', sourceWeight: 5 },
90
+ { name: 'National Weather Service API', url: 'https://www.weather.gov/documentation/services-web-api', description: 'US weather alerts, forecasts, observations, radar stations, and gridpoint weather data.', auth: 'No', https: true, cors: 'Yes', category: 'Weather', source: 'curated', sourceWeight: 5 },
91
+ { name: 'Pirate Weather', url: 'https://pirateweather.net/en/latest/', description: 'Weather forecast API compatible with Dark Sky-style forecast data.', auth: 'No', https: true, cors: 'Yes', category: 'Weather', source: 'curated', sourceWeight: 5 },
92
+ { name: 'Geocod.io', url: 'https://www.geocod.io/docs/', description: 'Forward and reverse geocoding, address parsing, coordinates, and census data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Geocoding', source: 'curated', sourceWeight: 5 },
93
+ { name: 'GraphHopper', url: 'https://docs.graphhopper.com/', description: 'Routing, navigation, route optimization, matrix, geocoding, and map matching APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Geocoding', source: 'curated', sourceWeight: 5 },
94
+ { name: 'GraphQL Jobs', url: 'https://graphql.jobs/docs/api/', description: 'GraphQL job listings and employment search API.', auth: 'No', https: true, cors: 'Yes', category: 'Jobs', source: 'curated', sourceWeight: 5 },
95
+ { name: 'Search.gov Jobs', url: 'https://search.gov/developer/jobs.html', description: 'US government job openings and federal jobs search API.', auth: 'No', https: true, cors: 'Unknown', category: 'Jobs', source: 'curated', sourceWeight: 5 },
96
+ { name: 'API-FOOTBALL', url: 'https://www.api-football.com/documentation-v3', description: 'Football/soccer fixtures, standings, teams, players, odds, predictions, and stats.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Sports & Fitness', source: 'curated', sourceWeight: 5 },
97
+ { name: 'Football-Data', url: 'https://www.football-data.org/documentation/quickstart', description: 'Football competitions, teams, fixtures, matches, standings, and scores.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Sports & Fitness', source: 'curated', sourceWeight: 5 },
98
+ { name: 'TVMaze', url: 'https://www.tvmaze.com/api', description: 'TV shows, episodes, schedules, cast, search, and show metadata API.', auth: 'No', https: true, cors: 'Yes', category: 'Entertainment', source: 'curated', sourceWeight: 5 },
99
+ { name: 'Jikan', url: 'https://docs.api.jikan.moe/', description: 'Unofficial MyAnimeList anime, manga, characters, rankings, and search API.', auth: 'No', https: true, cors: 'Yes', category: 'Anime', source: 'curated', sourceWeight: 5 },
100
+ { name: 'AniList', url: 'https://docs.anilist.co/', description: 'Anime and manga GraphQL API for titles, characters, studios, staff, and lists.', auth: 'OAuth', https: true, cors: 'Yes', category: 'Anime', source: 'curated', sourceWeight: 5 },
101
+ { name: 'GNews', url: 'https://gnews.io/docs/', description: 'News headlines, article search, topics, countries, and languages API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'News', source: 'curated', sourceWeight: 5 },
102
+ { name: 'Currents', url: 'https://currentsapi.services/en/docs/', description: 'Latest news, headlines, search, topics, regions, and languages API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'News', source: 'curated', sourceWeight: 5 },
103
+ { name: 'Data.gov', url: 'https://api.data.gov/docs/', description: 'US government API catalog and API key access for federal public data APIs.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Government', source: 'curated', sourceWeight: 5 },
104
+ { name: 'Open Food Facts', url: 'https://world.openfoodfacts.org/data', description: 'Food product database with barcodes, ingredients, nutrition, labels, and product metadata.', auth: 'No', https: true, cors: 'Yes', category: 'Food & Drink', source: 'curated', sourceWeight: 5 },
105
+ { name: 'Barcode Lookup', url: 'https://www.barcodelookup.com/api', description: 'Barcode, UPC, EAN, product lookup, catalog, pricing, images, and store product data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Shopping', source: 'curated', sourceWeight: 5 },
106
+ { name: 'OpenWeather', url: 'https://openweathermap.org/api', description: 'Weather forecasts, current weather, historical weather, alerts, geocoding, and maps.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Weather', source: 'curated', sourceWeight: 5 },
107
+ { name: 'News API', url: 'https://newsapi.org/', description: 'News headlines and article search across publishers and topics.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'News', source: 'curated', sourceWeight: 5 },
108
+ { name: 'The Guardian Open Platform', url: 'https://open-platform.theguardian.com/', description: 'Guardian articles, sections, tags, search, and content API.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'News', source: 'curated', sourceWeight: 5 },
109
+ { name: 'TMDb', url: 'https://developer.themoviedb.org/docs', description: 'Movie and TV metadata, ratings, posters, images, actors, and discovery.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Entertainment', source: 'curated', sourceWeight: 5 },
110
+ { name: 'OMDb', url: 'https://www.omdbapi.com/', description: 'Movie and TV metadata by IMDb ID or title.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Entertainment', source: 'curated', sourceWeight: 5 },
111
+ { name: 'OpenStreetMap Nominatim', url: 'https://nominatim.org/release-docs/latest/api/Overview/', description: 'OpenStreetMap geocoding and reverse geocoding API.', auth: 'No', https: true, cors: 'Yes', category: 'Geocoding', source: 'curated', sourceWeight: 5 },
112
+ { name: 'Mapbox', url: 'https://docs.mapbox.com/api/', description: 'Maps, geocoding, routing, navigation, tiles, and location APIs.', auth: 'apiKey', https: true, cors: 'Yes', category: 'Geocoding', source: 'curated', sourceWeight: 5 },
113
+ { name: 'USAJOBS', url: 'https://developer.usajobs.gov/', description: 'US federal government job listings and hiring data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Jobs', source: 'curated', sourceWeight: 5 },
114
+ { name: 'Adzuna', url: 'https://developer.adzuna.com/', description: 'Job search, salary, vacancies, and employment market data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Jobs', source: 'curated', sourceWeight: 5 },
115
+ { name: 'TheSportsDB', url: 'https://www.thesportsdb.com/api.php', description: 'Sports teams, leagues, events, scores, players, and media.', auth: 'apiKey', https: true, cors: 'Yes', category: 'Sports & Fitness', source: 'curated', sourceWeight: 5 },
116
+ { name: 'balldontlie', url: 'https://www.balldontlie.io/', description: 'Basketball/NBA teams, players, games, stats, and seasons.', auth: 'No', https: true, cors: 'Yes', category: 'Sports & Fitness', source: 'curated', sourceWeight: 5 },
117
+ { name: 'Census Data API', url: 'https://www.census.gov/data/developers/data-sets.html', description: 'US Census datasets, demographics, geography, ACS, and economic data.', auth: 'No', https: true, cors: 'Yes', category: 'Government', source: 'curated', sourceWeight: 5 },
118
+ { name: 'OpenFEC', url: 'https://api.open.fec.gov/developers/', description: 'US campaign finance, candidates, committees, filings, and election data.', auth: 'apiKey', https: true, cors: 'Unknown', category: 'Government', source: 'curated', sourceWeight: 5 },
119
+ { name: 'Fake Store API', url: 'https://fakestoreapi.com/', description: 'Fake ecommerce products, carts, users, and categories for demos and prototypes.', auth: 'No', https: true, cors: 'Yes', category: 'Shopping', source: 'curated', sourceWeight: 5 },
120
+ { name: 'Open Food Facts', url: 'https://world.openfoodfacts.org/data', description: 'Food product database with barcodes, ingredients, nutrition, and labels.', auth: 'No', https: true, cors: 'Yes', category: 'Food', source: 'curated', sourceWeight: 5 },
121
+ ];
122
+
123
+ function compactName(value) { return String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, ''); }
124
+ const KNOWN_BEST_NAMES = new Map(CURATED_APIS.map(api => [compactName(api.name), 15]));
125
+
31
126
  function detectDomains(queryTokens) {
32
127
  return Object.entries(DOMAIN_PROFILES)
33
128
  .filter(([, profile]) => profile.triggers.some(t => queryTokens.has(t)))
@@ -42,11 +137,12 @@ function domainAdjustment(entry, queryTokens) {
42
137
  let adjustment = 0;
43
138
  for (const domain of domains) {
44
139
  const profile = DOMAIN_PROFILES[domain];
45
- let categoryBoost = 0;
140
+ let categoryBoost = null;
46
141
  for (const [category, weight] of Object.entries(profile.categoryWeights || {})) {
47
- if (cat.includes(category)) categoryBoost = Math.max(categoryBoost, weight);
142
+ if (cat.includes(category)) categoryBoost = categoryBoost === null ? weight : Math.max(categoryBoost, weight);
48
143
  }
49
- const categoryHit = categoryBoost > 0;
144
+ categoryBoost = categoryBoost ?? 0;
145
+ const categoryHit = categoryBoost !== 0;
50
146
  const textHit = profile.boostTerms.some(t => text.includes(t));
51
147
  if (categoryHit) adjustment += categoryBoost;
52
148
  else if (textHit) adjustment += 3;
@@ -67,7 +163,7 @@ Usage:
67
163
 
68
164
  Options:
69
165
  --category <name> Filter by category substring
70
- --source <name> Filter by source: public-api-lists, public-apis, apis-guru
166
+ --source <name> Filter by source: public-api-lists, public-apis, apis-guru, curated
71
167
  --no-auth Only APIs with Auth = No
72
168
  --https Only HTTPS APIs
73
169
  --cors <value> Filter by CORS: Yes, No, Unknown
@@ -126,12 +222,26 @@ function textScore(entry, queryTokens) {
126
222
  + intersectionCount(queryTokens, all);
127
223
  }
128
224
 
225
+ function asciiRatio(value) {
226
+ const s = String(value || '');
227
+ if (!s.length) return 1;
228
+ let ascii = 0;
229
+ for (const ch of s) if (ch.charCodeAt(0) <= 127) ascii++;
230
+ return ascii / s.length;
231
+ }
232
+
129
233
  function score(entry, queryTokens) {
130
234
  let base = textScore(entry, queryTokens);
131
235
  if (entry.openapiUrl) base += 2;
132
236
  if (entry.sources?.length > 1) base += 2;
133
237
  if (entry.auth === 'No') base += 1;
134
238
  if (entry.https) base += 1;
239
+ if (asciiRatio(entry.name) < 0.7) base -= 60;
240
+ else if (asciiRatio(`${entry.name || ''} ${entry.description || ''}`) < 0.65) base -= 18;
241
+ const compactEntryName = compactName(entry.name);
242
+ for (const [name, weight] of KNOWN_BEST_NAMES) {
243
+ if (compactEntryName.includes(name) || name.includes(compactEntryName)) base += weight;
244
+ }
135
245
  return base + domainAdjustment(entry, queryTokens);
136
246
  }
137
247
 
@@ -252,12 +362,16 @@ async function buildData() {
252
362
  sourceStatus['apis-guru'] = rows.length;
253
363
  entries.push(...rows);
254
364
  } else sourceStatus['apis-guru'] = `error: ${guru.reason.message}`;
365
+ sourceStatus.curated = CURATED_APIS.length;
366
+ entries.push(...CURATED_APIS);
255
367
  return { generatedAt: new Date().toISOString(), sourceStatus, entries: dedupe(entries) };
256
368
  }
257
369
 
258
370
  function keyFor(entry) {
371
+ const compact = compactName(entry.name);
372
+ if (KNOWN_BEST_NAMES.has(compact)) return `known:${compact}`;
259
373
  const host = String(entry.url || '').toLowerCase().replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0];
260
- return `${String(entry.name || '').toLowerCase().replace(/[^a-z0-9]+/g, '')}|${host}`;
374
+ return `${compact}|${host}`;
261
375
  }
262
376
 
263
377
  function mergeEntry(a, b) {
@@ -325,7 +439,8 @@ function filterEntries(entries, args) {
325
439
  if (args.openapi && !e.openapiUrl) return [];
326
440
  if (args.cors && String(e.cors || '').toLowerCase() !== args.cors.toLowerCase()) return [];
327
441
  const matched = q.size ? textScore(e, q) : 1;
328
- if (q.size && matched === 0) return [];
442
+ const domain = q.size ? domainAdjustment(e, q) : 0;
443
+ if (q.size && matched === 0 && domain <= 0) return [];
329
444
  const s = q.size ? score(e, q) : 1;
330
445
  if (q.size && s <= 0) return [];
331
446
  return [{ ...e, score: s + (e.sourceWeight || 0) }];