@ls-apis/cli 0.0.8 → 0.0.9
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/data/apis.json +119 -19
- package/dist/index.js +0 -0
- package/package.json +1 -1
- package/dist/config.js +0 -36
- package/dist/packages/cli/src/categories.js +0 -13
- package/dist/packages/cli/src/colors.js +0 -19
- package/dist/packages/cli/src/config.js +0 -36
- package/dist/packages/cli/src/formatter.js +0 -73
- package/dist/packages/cli/src/index.js +0 -178
- package/dist/packages/cli/src/providers.js +0 -21
- package/dist/packages/cli/src/qa.js +0 -14
- package/dist/packages/cli/src/search.js +0 -50
- package/dist/packages/cli/src/types.js +0 -1
- package/dist/search.js +0 -50
- package/dist/types.js +0 -1
package/data/apis.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2026-06-
|
|
2
|
+
"timestamp": "2026-06-14T07:35:14.690Z",
|
|
3
3
|
"providers": [
|
|
4
4
|
{
|
|
5
5
|
"name": "apis-guru",
|
|
@@ -26565,6 +26565,16 @@
|
|
|
26565
26565
|
"openapiSpec": null,
|
|
26566
26566
|
"sources": ["github-public-apis"]
|
|
26567
26567
|
},
|
|
26568
|
+
{
|
|
26569
|
+
"name": "Pick an Agency",
|
|
26570
|
+
"description": "Search 47,000+ marketing agencies by service, location and rating",
|
|
26571
|
+
"link": "https://www.pickanagency.com/developers",
|
|
26572
|
+
"auth": "no",
|
|
26573
|
+
"cors": "yes",
|
|
26574
|
+
"categories": ["Business"],
|
|
26575
|
+
"openapiSpec": null,
|
|
26576
|
+
"sources": ["github-public-apis"]
|
|
26577
|
+
},
|
|
26568
26578
|
{
|
|
26569
26579
|
"name": "Redash",
|
|
26570
26580
|
"description": "Access your queries and dashboards on Redash",
|
|
@@ -27555,6 +27565,16 @@
|
|
|
27555
27565
|
"openapiSpec": null,
|
|
27556
27566
|
"sources": ["github-public-apis"]
|
|
27557
27567
|
},
|
|
27568
|
+
{
|
|
27569
|
+
"name": "Hyperliquid Market Data",
|
|
27570
|
+
"description": "Hyperliquid open interest, funding and cross-venue predicted rates per coin",
|
|
27571
|
+
"link": "https://rapidapi.com/theliminalguy/api/hyperliquid-market-data-oi-funding-open-interest",
|
|
27572
|
+
"auth": "apiKey",
|
|
27573
|
+
"cors": "yes",
|
|
27574
|
+
"categories": ["Cryptocurrency"],
|
|
27575
|
+
"openapiSpec": null,
|
|
27576
|
+
"sources": ["github-public-apis"]
|
|
27577
|
+
},
|
|
27558
27578
|
{
|
|
27559
27579
|
"name": "icy.tools",
|
|
27560
27580
|
"description": "GraphQL based NFT API",
|
|
@@ -30913,7 +30933,7 @@
|
|
|
30913
30933
|
"cors": "unknown",
|
|
30914
30934
|
"categories": ["Food & Drink"],
|
|
30915
30935
|
"openapiSpec": null,
|
|
30916
|
-
"sources": ["github-public-apis"
|
|
30936
|
+
"sources": ["github-public-apis"]
|
|
30917
30937
|
},
|
|
30918
30938
|
{
|
|
30919
30939
|
"name": "Foodish",
|
|
@@ -32535,6 +32555,16 @@
|
|
|
32535
32555
|
"openapiSpec": null,
|
|
32536
32556
|
"sources": ["github-public-apis"]
|
|
32537
32557
|
},
|
|
32558
|
+
{
|
|
32559
|
+
"name": "IP-API.io",
|
|
32560
|
+
"description": "IP geolocation with VPN/proxy/Tor detection, reputation and risk score",
|
|
32561
|
+
"link": "https://ip-api.io",
|
|
32562
|
+
"auth": "apiKey",
|
|
32563
|
+
"cors": "yes",
|
|
32564
|
+
"categories": ["Geocoding"],
|
|
32565
|
+
"openapiSpec": null,
|
|
32566
|
+
"sources": ["github-public-apis"]
|
|
32567
|
+
},
|
|
32538
32568
|
{
|
|
32539
32569
|
"name": "IP Geolocation",
|
|
32540
32570
|
"description": "Geolocate website visitors from their IPs",
|
|
@@ -32655,6 +32685,16 @@
|
|
|
32655
32685
|
"openapiSpec": null,
|
|
32656
32686
|
"sources": ["github-public-apis"]
|
|
32657
32687
|
},
|
|
32688
|
+
{
|
|
32689
|
+
"name": "LatLng",
|
|
32690
|
+
"description": "Geocoding, reverse geocoding, places, and static maps",
|
|
32691
|
+
"link": "https://www.latlng.work/docs",
|
|
32692
|
+
"auth": "no",
|
|
32693
|
+
"cors": "yes",
|
|
32694
|
+
"categories": ["Geocoding"],
|
|
32695
|
+
"openapiSpec": null,
|
|
32696
|
+
"sources": ["github-public-apis"]
|
|
32697
|
+
},
|
|
32658
32698
|
{
|
|
32659
32699
|
"name": "LocationIQ",
|
|
32660
32700
|
"description": "Provides forward/reverse geocoding and batch geocoding",
|
|
@@ -34115,6 +34155,16 @@
|
|
|
34115
34155
|
"openapiSpec": null,
|
|
34116
34156
|
"sources": ["github-public-apis"]
|
|
34117
34157
|
},
|
|
34158
|
+
{
|
|
34159
|
+
"name": "Cure Cancer With AI",
|
|
34160
|
+
"description": "Oncology research, clinical trials, FDA approvals, news, and MAMMAL predictions",
|
|
34161
|
+
"link": "https://www.curecancerwithai.com/developers",
|
|
34162
|
+
"auth": "apiKey",
|
|
34163
|
+
"cors": "no",
|
|
34164
|
+
"categories": ["Health"],
|
|
34165
|
+
"openapiSpec": null,
|
|
34166
|
+
"sources": ["github-public-apis"]
|
|
34167
|
+
},
|
|
34118
34168
|
{
|
|
34119
34169
|
"name": "Dataflow Kit COVID-19",
|
|
34120
34170
|
"description": "COVID-19 live statistics into sites per hour",
|
|
@@ -35265,6 +35315,16 @@
|
|
|
35265
35315
|
"openapiSpec": null,
|
|
35266
35316
|
"sources": ["github-public-apis", "publicapis-dev"]
|
|
35267
35317
|
},
|
|
35318
|
+
{
|
|
35319
|
+
"name": "Noozra",
|
|
35320
|
+
"description": "Free news headlines from 200+ curated RSS sources",
|
|
35321
|
+
"link": "https://noozra.com/api",
|
|
35322
|
+
"auth": "no",
|
|
35323
|
+
"cors": "yes",
|
|
35324
|
+
"categories": ["News"],
|
|
35325
|
+
"openapiSpec": null,
|
|
35326
|
+
"sources": ["github-public-apis"]
|
|
35327
|
+
},
|
|
35268
35328
|
{
|
|
35269
35329
|
"name": "NPR One",
|
|
35270
35330
|
"description": "Personalized news listening experience from NPR",
|
|
@@ -35445,6 +35505,16 @@
|
|
|
35445
35505
|
"openapiSpec": null,
|
|
35446
35506
|
"sources": ["github-public-apis"]
|
|
35447
35507
|
},
|
|
35508
|
+
{
|
|
35509
|
+
"name": "InfraNode",
|
|
35510
|
+
"description": "Unified German city open data: weather, air quality, EV chargers, transit, demographics",
|
|
35511
|
+
"link": "https://infranode.dev",
|
|
35512
|
+
"auth": "apiKey",
|
|
35513
|
+
"cors": "unknown",
|
|
35514
|
+
"categories": ["Open Data"],
|
|
35515
|
+
"openapiSpec": null,
|
|
35516
|
+
"sources": ["github-public-apis"]
|
|
35517
|
+
},
|
|
35448
35518
|
{
|
|
35449
35519
|
"name": "Joshua Project",
|
|
35450
35520
|
"description": "People groups of the world with the fewest followers of Christ",
|
|
@@ -35455,6 +35525,16 @@
|
|
|
35455
35525
|
"openapiSpec": null,
|
|
35456
35526
|
"sources": ["github-public-apis"]
|
|
35457
35527
|
},
|
|
35528
|
+
{
|
|
35529
|
+
"name": "K-Data Gate",
|
|
35530
|
+
"description": "Korean market data: K-beauty/K-food products, Naver trends, stocks, real estate, weather",
|
|
35531
|
+
"link": "https://kdata-gate.vercel.app/docs",
|
|
35532
|
+
"auth": "apiKey",
|
|
35533
|
+
"cors": "unknown",
|
|
35534
|
+
"categories": ["Open Data"],
|
|
35535
|
+
"openapiSpec": null,
|
|
35536
|
+
"sources": ["github-public-apis"]
|
|
35537
|
+
},
|
|
35458
35538
|
{
|
|
35459
35539
|
"name": "Kaggle",
|
|
35460
35540
|
"description": "Create and interact with Datasets, Notebooks, and connect with Kaggle",
|
|
@@ -36185,6 +36265,16 @@
|
|
|
36185
36265
|
"openapiSpec": null,
|
|
36186
36266
|
"sources": ["github-public-apis", "publicapis-dev"]
|
|
36187
36267
|
},
|
|
36268
|
+
{
|
|
36269
|
+
"name": "VeriRoute Intel",
|
|
36270
|
+
"description": "CNAM caller ID, carrier/LRN lookup and spam scoring for North American phone numbers",
|
|
36271
|
+
"link": "https://verirouteintel.com",
|
|
36272
|
+
"auth": "apiKey",
|
|
36273
|
+
"cors": "unknown",
|
|
36274
|
+
"categories": ["Phone"],
|
|
36275
|
+
"openapiSpec": null,
|
|
36276
|
+
"sources": ["github-public-apis"]
|
|
36277
|
+
},
|
|
36188
36278
|
{
|
|
36189
36279
|
"name": "Screenshotlayer",
|
|
36190
36280
|
"description": "URL to screenshot",
|
|
@@ -39673,7 +39763,7 @@
|
|
|
39673
39763
|
"cors": "yes",
|
|
39674
39764
|
"categories": ["Url Shorteners"],
|
|
39675
39765
|
"openapiSpec": null,
|
|
39676
|
-
"sources": ["github-public-apis"
|
|
39766
|
+
"sources": ["github-public-apis"]
|
|
39677
39767
|
},
|
|
39678
39768
|
{
|
|
39679
39769
|
"name": "Mgnet.me",
|
|
@@ -39683,7 +39773,7 @@
|
|
|
39683
39773
|
"cors": "no",
|
|
39684
39774
|
"categories": ["Url Shorteners"],
|
|
39685
39775
|
"openapiSpec": null,
|
|
39686
|
-
"sources": ["github-public-apis"
|
|
39776
|
+
"sources": ["github-public-apis"]
|
|
39687
39777
|
},
|
|
39688
39778
|
{
|
|
39689
39779
|
"name": "owo",
|
|
@@ -41455,6 +41545,16 @@
|
|
|
41455
41545
|
"openapiSpec": null,
|
|
41456
41546
|
"sources": ["publicapis-dev"]
|
|
41457
41547
|
},
|
|
41548
|
+
{
|
|
41549
|
+
"name": "DATPAQ",
|
|
41550
|
+
"description": "Developer-first APIs. Enterprise-ready infrastructure. Sampla data, avatars, image processing and a lot more.",
|
|
41551
|
+
"link": "https://datpaq.com",
|
|
41552
|
+
"auth": "apiKey",
|
|
41553
|
+
"cors": null,
|
|
41554
|
+
"categories": ["Development"],
|
|
41555
|
+
"openapiSpec": null,
|
|
41556
|
+
"sources": ["publicapis-dev"]
|
|
41557
|
+
},
|
|
41458
41558
|
{
|
|
41459
41559
|
"name": "PageBolt",
|
|
41460
41560
|
"description": "Screenshot, PDF, video recording, OG image, and page inspection via REST API.",
|
|
@@ -41615,16 +41715,6 @@
|
|
|
41615
41715
|
"openapiSpec": null,
|
|
41616
41716
|
"sources": ["publicapis-dev"]
|
|
41617
41717
|
},
|
|
41618
|
-
{
|
|
41619
|
-
"name": "Piloterr",
|
|
41620
|
-
"description": "Piloterr web scraping API handles headless browsers, rotates proxies for you, and offers json-parsed data extraction.",
|
|
41621
|
-
"link": "https://www.piloterr.com/",
|
|
41622
|
-
"auth": "apiKey",
|
|
41623
|
-
"cors": null,
|
|
41624
|
-
"categories": ["Development"],
|
|
41625
|
-
"openapiSpec": null,
|
|
41626
|
-
"sources": ["publicapis-dev"]
|
|
41627
|
-
},
|
|
41628
41718
|
{
|
|
41629
41719
|
"name": "PDFBolt",
|
|
41630
41720
|
"description": "High-quality HTML to PDF conversion with templates and AI generation.",
|
|
@@ -42265,6 +42355,16 @@
|
|
|
42265
42355
|
"openapiSpec": null,
|
|
42266
42356
|
"sources": ["publicapis-dev"]
|
|
42267
42357
|
},
|
|
42358
|
+
{
|
|
42359
|
+
"name": "Your Move - Nutrition API",
|
|
42360
|
+
"description": "A complete nutrition API. Search foods, get nutrition facts and values. Generate meal plans and look up packaged products from 180+ countries worldwide.",
|
|
42361
|
+
"link": "https://ymove.app/nutrition-api",
|
|
42362
|
+
"auth": "apiKey",
|
|
42363
|
+
"cors": null,
|
|
42364
|
+
"categories": ["Food & Drink"],
|
|
42365
|
+
"openapiSpec": null,
|
|
42366
|
+
"sources": ["publicapis-dev"]
|
|
42367
|
+
},
|
|
42268
42368
|
{
|
|
42269
42369
|
"name": "LCBO",
|
|
42270
42370
|
"description": null,
|
|
@@ -43806,8 +43906,8 @@
|
|
|
43806
43906
|
"sources": ["publicapis-dev"]
|
|
43807
43907
|
},
|
|
43808
43908
|
{
|
|
43809
|
-
"name": "
|
|
43810
|
-
"description": "
|
|
43909
|
+
"name": "Your Move - Exercise API",
|
|
43910
|
+
"description": "The complete exercise library and fitness API for your app. 698+ professional HD exercise videos with workout generation and program builder.",
|
|
43811
43911
|
"link": "https://ymove.app/exercise-api",
|
|
43812
43912
|
"auth": "apiKey",
|
|
43813
43913
|
"cors": null,
|
|
@@ -44176,9 +44276,9 @@
|
|
|
44176
44276
|
"sources": ["publicapis-dev"]
|
|
44177
44277
|
},
|
|
44178
44278
|
{
|
|
44179
|
-
"name": "
|
|
44180
|
-
"description": "
|
|
44181
|
-
"link": "https://
|
|
44279
|
+
"name": "Linkly",
|
|
44280
|
+
"description": "URL shortener API with branded domains, click tracking, smart redirects, and webhooks.",
|
|
44281
|
+
"link": "https://linklyhq.com/url-shortener-api",
|
|
44182
44282
|
"auth": "apiKey",
|
|
44183
44283
|
"cors": null,
|
|
44184
44284
|
"categories": ["Url Shorteners"],
|
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/dist/config.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
export const CONFIG_PATH = join(homedir(), '.ls-apis');
|
|
5
|
-
const DEFAULTS = {
|
|
6
|
-
limit: 20,
|
|
7
|
-
descriptionMaxLength: 250,
|
|
8
|
-
colors: true,
|
|
9
|
-
};
|
|
10
|
-
export async function loadConfig() {
|
|
11
|
-
try {
|
|
12
|
-
const raw = await readFile(CONFIG_PATH, 'utf-8');
|
|
13
|
-
const parsed = JSON.parse(raw);
|
|
14
|
-
return {
|
|
15
|
-
limit: parsed.limit ?? DEFAULTS.limit,
|
|
16
|
-
descriptionMaxLength: parsed.descriptionMaxLength ?? DEFAULTS.descriptionMaxLength,
|
|
17
|
-
colors: parsed.colors ?? DEFAULTS.colors,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
await ensureConfigExists();
|
|
22
|
-
return DEFAULTS;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async function ensureConfigExists() {
|
|
26
|
-
try {
|
|
27
|
-
await access(CONFIG_PATH);
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
await writeFile(CONFIG_PATH, JSON.stringify(DEFAULTS, null, 2) + '\n');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export async function getConfig() {
|
|
34
|
-
const config = await loadConfig();
|
|
35
|
-
return { config, filePath: CONFIG_PATH };
|
|
36
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { getCategories } from './search.js';
|
|
2
|
-
import { formatList } from './formatter.js';
|
|
3
|
-
import { initColors } from './colors.js';
|
|
4
|
-
export function handleCategories(apis, argv, config) {
|
|
5
|
-
const noColor = argv.color === false;
|
|
6
|
-
initColors(noColor ?? !config.colors);
|
|
7
|
-
const categories = getCategories(apis);
|
|
8
|
-
const output = formatList(categories, 'categories', {
|
|
9
|
-
sort: argv.sort,
|
|
10
|
-
output: argv.output,
|
|
11
|
-
});
|
|
12
|
-
console.log(output);
|
|
13
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
let useColor = false;
|
|
3
|
-
export function initColors(noColor) {
|
|
4
|
-
if (noColor === true || process.env.NO_COLOR) {
|
|
5
|
-
useColor = false;
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
useColor = true;
|
|
9
|
-
}
|
|
10
|
-
chalk.level = useColor ? 3 : 0;
|
|
11
|
-
}
|
|
12
|
-
export const color = {
|
|
13
|
-
bold: (text) => (useColor ? chalk.bold(text) : text),
|
|
14
|
-
dim: (text) => (useColor ? chalk.dim(text) : text),
|
|
15
|
-
cyan: (text) => (useColor ? chalk.cyan(text) : text),
|
|
16
|
-
green: (text) => (useColor ? chalk.green(text) : text),
|
|
17
|
-
yellow: (text) => (useColor ? chalk.yellow(text) : text),
|
|
18
|
-
red: (text) => (useColor ? chalk.red(text) : text),
|
|
19
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
export const CONFIG_PATH = join(homedir(), '.ls-apis');
|
|
5
|
-
const DEFAULTS = {
|
|
6
|
-
limit: 20,
|
|
7
|
-
descriptionMaxLength: 250,
|
|
8
|
-
colors: true,
|
|
9
|
-
};
|
|
10
|
-
export async function loadConfig() {
|
|
11
|
-
try {
|
|
12
|
-
const raw = await readFile(CONFIG_PATH, 'utf-8');
|
|
13
|
-
const parsed = JSON.parse(raw);
|
|
14
|
-
return {
|
|
15
|
-
limit: parsed.limit ?? DEFAULTS.limit,
|
|
16
|
-
descriptionMaxLength: parsed.descriptionMaxLength ?? DEFAULTS.descriptionMaxLength,
|
|
17
|
-
colors: parsed.colors ?? DEFAULTS.colors,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
await ensureConfigExists();
|
|
22
|
-
return DEFAULTS;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async function ensureConfigExists() {
|
|
26
|
-
try {
|
|
27
|
-
await access(CONFIG_PATH);
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
await writeFile(CONFIG_PATH, JSON.stringify(DEFAULTS, null, 2) + '\n');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export async function getConfig() {
|
|
34
|
-
const config = await loadConfig();
|
|
35
|
-
return { config, filePath: CONFIG_PATH };
|
|
36
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { color } from './colors.js';
|
|
2
|
-
function truncate(text, maxLength) {
|
|
3
|
-
if (text.length <= maxLength) {
|
|
4
|
-
return text;
|
|
5
|
-
}
|
|
6
|
-
return text.slice(0, maxLength) + '...';
|
|
7
|
-
}
|
|
8
|
-
function formatText(results, total, limit, options) {
|
|
9
|
-
const maxLen = options.descriptionMaxLength ?? 250;
|
|
10
|
-
const lines = [];
|
|
11
|
-
lines.push(color.bold(`Found ${total} APIs:`));
|
|
12
|
-
for (const api of results.slice(0, limit)) {
|
|
13
|
-
lines.push(color.cyan(` ${api.name}`));
|
|
14
|
-
lines.push(` ${color.dim('Description:')} ${truncate(api.description ?? 'No description', maxLen)}`);
|
|
15
|
-
lines.push(` ${color.dim('Link:')} ${api.link}`);
|
|
16
|
-
// Test missing braces - should trigger ESLint error
|
|
17
|
-
if (api.auth !== undefined && api.auth !== null) {
|
|
18
|
-
lines.push(` ${color.dim('Auth:')} ${color.yellow(api.auth)}`);
|
|
19
|
-
}
|
|
20
|
-
if (api.categories.length > 0) {
|
|
21
|
-
lines.push(` ${color.dim('Categories:')} ${color.green(api.categories.join(', '))}`);
|
|
22
|
-
}
|
|
23
|
-
if (api.openapiSpec !== undefined && api.openapiSpec !== null) {
|
|
24
|
-
lines.push(` ${color.dim('OpenAPI Spec:')} ${api.openapiSpec}`);
|
|
25
|
-
}
|
|
26
|
-
if (api.sources.length > 0) {
|
|
27
|
-
lines.push(` ${color.dim('Sources:')} ${api.sources.join(', ')}`);
|
|
28
|
-
}
|
|
29
|
-
lines.push('');
|
|
30
|
-
}
|
|
31
|
-
if (results.length > limit) {
|
|
32
|
-
lines.push(` ${color.dim('... and ' + (results.length - limit) + ' more')}`);
|
|
33
|
-
}
|
|
34
|
-
return lines.join('\n');
|
|
35
|
-
}
|
|
36
|
-
function formatJson(results, limit) {
|
|
37
|
-
return JSON.stringify(results.slice(0, limit), null, 2);
|
|
38
|
-
}
|
|
39
|
-
export function formatResults(results, total, limit, options) {
|
|
40
|
-
if (options.output === 'json') {
|
|
41
|
-
return formatJson(results, limit);
|
|
42
|
-
}
|
|
43
|
-
return formatText(results, total, limit, options);
|
|
44
|
-
}
|
|
45
|
-
export function formatList(items, label, options) {
|
|
46
|
-
const sorted = [...items.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
47
|
-
if (options.sort === 'count') {
|
|
48
|
-
sorted.sort((a, b) => b[1] - a[1]);
|
|
49
|
-
}
|
|
50
|
-
if (options.output === 'json') {
|
|
51
|
-
return JSON.stringify(sorted.map(([name, count]) => ({ name, count })), null, 2);
|
|
52
|
-
}
|
|
53
|
-
const lines = [`Found ${sorted.length} ${label}:`];
|
|
54
|
-
for (const [name, count] of sorted) {
|
|
55
|
-
lines.push(` ${name.padEnd(20)} (${count} APIs)`);
|
|
56
|
-
}
|
|
57
|
-
return lines.join('\n');
|
|
58
|
-
}
|
|
59
|
-
export function formatProviders(providers, options) {
|
|
60
|
-
const sorted = [...providers].sort((a, b) => a.name.localeCompare(b.name));
|
|
61
|
-
if (options.sort === 'count') {
|
|
62
|
-
sorted.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
63
|
-
}
|
|
64
|
-
if (options.output === 'json') {
|
|
65
|
-
return JSON.stringify(sorted, null, 2);
|
|
66
|
-
}
|
|
67
|
-
const lines = [`Found ${sorted.length} providers:`];
|
|
68
|
-
for (const provider of sorted) {
|
|
69
|
-
const countStr = provider.count !== undefined ? ` (${provider.count} APIs)` : '';
|
|
70
|
-
lines.push(` ${provider.name.padEnd(20)} ${color.dim(provider.url)}${countStr}`);
|
|
71
|
-
}
|
|
72
|
-
return lines.join('\n');
|
|
73
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import yargs from 'yargs';
|
|
6
|
-
import { hideBin } from 'yargs/helpers';
|
|
7
|
-
import { initColors } from './colors.js';
|
|
8
|
-
import { loadConfig, getConfig } from './config.js';
|
|
9
|
-
import { search } from './search.js';
|
|
10
|
-
import { formatResults } from './formatter.js';
|
|
11
|
-
import { handleCategories } from './categories.js';
|
|
12
|
-
import { handleProviders } from './providers.js';
|
|
13
|
-
import { runQa } from './qa.js';
|
|
14
|
-
let version;
|
|
15
|
-
async function getVersion() {
|
|
16
|
-
if (!version) {
|
|
17
|
-
const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../package.json');
|
|
18
|
-
const pkg = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
|
19
|
-
version = pkg.version;
|
|
20
|
-
}
|
|
21
|
-
return version;
|
|
22
|
-
}
|
|
23
|
-
export async function run(argv) {
|
|
24
|
-
const config = await loadConfig();
|
|
25
|
-
const ver = await getVersion();
|
|
26
|
-
const dataFile = join(dirname(fileURLToPath(import.meta.url)), '../data/apis.json');
|
|
27
|
-
const data = await readFile(dataFile, 'utf-8');
|
|
28
|
-
const { apis, providers } = JSON.parse(data);
|
|
29
|
-
let exitEarly = false;
|
|
30
|
-
const args = await yargs(argv)
|
|
31
|
-
.scriptName('ls-apis')
|
|
32
|
-
.strict()
|
|
33
|
-
.version(ver)
|
|
34
|
-
.alias('version', 'V')
|
|
35
|
-
.alias('version', 'v')
|
|
36
|
-
.usage('$0 <command> [options]')
|
|
37
|
-
.usage('$0 [options]')
|
|
38
|
-
.example('$0 -q weather', 'Search for weather APIs')
|
|
39
|
-
.example('$0 -c weather', 'Filter by weather category')
|
|
40
|
-
.example('$0 -q weather -c storage', 'Search weather in storage category')
|
|
41
|
-
.example('$0 -a oauth', 'Filter by OAuth auth')
|
|
42
|
-
.example('$0 -q weather -l 50', 'Limit results to 50')
|
|
43
|
-
.example('$0 -q weather -o json', 'Output as JSON')
|
|
44
|
-
.example('$0 -q weather -s name', 'Sort results by name')
|
|
45
|
-
.command({
|
|
46
|
-
command: 'categories',
|
|
47
|
-
describe: 'List all API categories',
|
|
48
|
-
builder: (yargs) => {
|
|
49
|
-
return yargs
|
|
50
|
-
.option('sort', {
|
|
51
|
-
alias: 's',
|
|
52
|
-
type: 'string',
|
|
53
|
-
choices: ['name', 'count'],
|
|
54
|
-
default: 'name',
|
|
55
|
-
describe: 'Sort by name or count',
|
|
56
|
-
})
|
|
57
|
-
.option('output', {
|
|
58
|
-
alias: 'o',
|
|
59
|
-
type: 'string',
|
|
60
|
-
choices: ['text', 'json'],
|
|
61
|
-
default: 'text',
|
|
62
|
-
describe: 'Output format',
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
handler: (argv) => {
|
|
66
|
-
handleCategories(apis, argv, config);
|
|
67
|
-
exitEarly = true;
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
.command({
|
|
71
|
-
command: 'providers',
|
|
72
|
-
describe: 'List all data providers',
|
|
73
|
-
builder: (yargs) => {
|
|
74
|
-
return yargs
|
|
75
|
-
.option('sort', {
|
|
76
|
-
alias: 's',
|
|
77
|
-
type: 'string',
|
|
78
|
-
choices: ['name', 'count'],
|
|
79
|
-
default: 'name',
|
|
80
|
-
describe: 'Sort by name or count',
|
|
81
|
-
})
|
|
82
|
-
.option('output', {
|
|
83
|
-
alias: 'o',
|
|
84
|
-
type: 'string',
|
|
85
|
-
choices: ['text', 'json'],
|
|
86
|
-
default: 'text',
|
|
87
|
-
describe: 'Output format',
|
|
88
|
-
});
|
|
89
|
-
},
|
|
90
|
-
handler: (argv) => {
|
|
91
|
-
handleProviders(providers, apis, argv, config);
|
|
92
|
-
exitEarly = true;
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
.command({
|
|
96
|
-
command: 'config',
|
|
97
|
-
describe: 'Show config settings',
|
|
98
|
-
handler: async () => {
|
|
99
|
-
const { config, filePath } = await getConfig();
|
|
100
|
-
console.log(`Config file: ${filePath}`);
|
|
101
|
-
console.log(JSON.stringify(config, null, 2));
|
|
102
|
-
exitEarly = true;
|
|
103
|
-
},
|
|
104
|
-
})
|
|
105
|
-
.command({
|
|
106
|
-
command: 'qa',
|
|
107
|
-
describe: 'Run QA validation on apis.json',
|
|
108
|
-
builder: (yargs) => {
|
|
109
|
-
return yargs.option('file', {
|
|
110
|
-
alias: 'f',
|
|
111
|
-
type: 'string',
|
|
112
|
-
describe: 'Custom output file path (default: qa-output/issues.json)',
|
|
113
|
-
});
|
|
114
|
-
},
|
|
115
|
-
handler: async (argv) => {
|
|
116
|
-
await runQa(config.descriptionMaxLength, argv.file);
|
|
117
|
-
exitEarly = true;
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
.option('query', {
|
|
121
|
-
alias: 'q',
|
|
122
|
-
type: 'string',
|
|
123
|
-
describe: 'Search query (filters name, description)',
|
|
124
|
-
})
|
|
125
|
-
.option('category', { alias: 'c', type: 'string', describe: 'Filter by category' })
|
|
126
|
-
.option('auth', {
|
|
127
|
-
alias: 'a',
|
|
128
|
-
type: 'string',
|
|
129
|
-
describe: 'Filter by auth (apiKey, OAuth, no)',
|
|
130
|
-
})
|
|
131
|
-
.option('limit', {
|
|
132
|
-
alias: 'l',
|
|
133
|
-
type: 'number',
|
|
134
|
-
default: config.limit,
|
|
135
|
-
describe: 'Max results to show',
|
|
136
|
-
})
|
|
137
|
-
.option('output', {
|
|
138
|
-
alias: 'o',
|
|
139
|
-
type: 'string',
|
|
140
|
-
choices: ['text', 'json'],
|
|
141
|
-
default: 'text',
|
|
142
|
-
describe: 'Output format',
|
|
143
|
-
})
|
|
144
|
-
.option('sort', {
|
|
145
|
-
alias: 's',
|
|
146
|
-
type: 'string',
|
|
147
|
-
choices: ['name', 'category', 'auth'],
|
|
148
|
-
describe: 'Sort results by field',
|
|
149
|
-
})
|
|
150
|
-
.option('no-color', { type: 'boolean', describe: 'Disable colors in output' })
|
|
151
|
-
.help()
|
|
152
|
-
.alias('help', 'h')
|
|
153
|
-
.alias('help', '?')
|
|
154
|
-
.parse();
|
|
155
|
-
if (exitEarly) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const noColor = args.color === false;
|
|
159
|
-
initColors(noColor ?? !config.colors);
|
|
160
|
-
const results = search(apis, {
|
|
161
|
-
query: args.query,
|
|
162
|
-
category: args.category,
|
|
163
|
-
auth: args.auth,
|
|
164
|
-
sort: args.sort,
|
|
165
|
-
limit: args.limit,
|
|
166
|
-
});
|
|
167
|
-
const output = formatResults(results, results.length, args.limit, {
|
|
168
|
-
output: args.output,
|
|
169
|
-
descriptionMaxLength: config.descriptionMaxLength,
|
|
170
|
-
});
|
|
171
|
-
console.log(output);
|
|
172
|
-
}
|
|
173
|
-
if (process.argv[1] && !process.argv[1].includes('vitest')) {
|
|
174
|
-
run(hideBin(process.argv)).catch((err) => {
|
|
175
|
-
console.error('Error:', err.message);
|
|
176
|
-
process.exit(1);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { formatProviders } from './formatter.js';
|
|
2
|
-
import { initColors } from './colors.js';
|
|
3
|
-
export function handleProviders(providers, apis, argv, config) {
|
|
4
|
-
const noColor = argv.color === false;
|
|
5
|
-
initColors(noColor ?? !config.colors);
|
|
6
|
-
const counts = new Map();
|
|
7
|
-
for (const api of apis) {
|
|
8
|
-
for (const source of api.sources) {
|
|
9
|
-
counts.set(source, (counts.get(source) ?? 0) + 1);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
const providersWithCounts = providers.map((p) => ({
|
|
13
|
-
...p,
|
|
14
|
-
count: counts.get(p.name) ?? 0,
|
|
15
|
-
}));
|
|
16
|
-
const output = formatProviders(providersWithCounts, {
|
|
17
|
-
sort: argv.sort,
|
|
18
|
-
output: argv.output,
|
|
19
|
-
});
|
|
20
|
-
console.log(output);
|
|
21
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { realpathSync } from 'node:fs';
|
|
5
|
-
export async function runQa(descriptionMaxLength, outputFile) {
|
|
6
|
-
const currentDir = dirname(realpathSync(fileURLToPath(import.meta.url)));
|
|
7
|
-
const projectRoot = join(currentDir, '../../..');
|
|
8
|
-
const script = join(projectRoot, 'packages/aggregator/src/qa/index.ts');
|
|
9
|
-
const args = ['npx', 'tsx', script];
|
|
10
|
-
if (outputFile) {
|
|
11
|
-
args.push('--output', outputFile);
|
|
12
|
-
}
|
|
13
|
-
execSync(args.join(' '), { cwd: projectRoot, stdio: 'inherit' });
|
|
14
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export function search(apis, options) {
|
|
2
|
-
let results = apis;
|
|
3
|
-
if (options.query) {
|
|
4
|
-
const q = options.query.toLowerCase();
|
|
5
|
-
results = results.filter((api) => api.name.toLowerCase().includes(q) || api.description?.toLowerCase().includes(q));
|
|
6
|
-
}
|
|
7
|
-
if (options.category) {
|
|
8
|
-
const cat = options.category.toLowerCase();
|
|
9
|
-
results = results.filter((api) => api.categories.some((c) => c.toLowerCase().includes(cat)));
|
|
10
|
-
}
|
|
11
|
-
if (options.auth) {
|
|
12
|
-
const auth = options.auth.toLowerCase();
|
|
13
|
-
results = results.filter((api) => {
|
|
14
|
-
if (auth === 'no') {
|
|
15
|
-
return !api.auth;
|
|
16
|
-
}
|
|
17
|
-
return api.auth?.toLowerCase().includes(auth);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
if (options.sort) {
|
|
21
|
-
results = [...results].sort((a, b) => {
|
|
22
|
-
switch (options.sort) {
|
|
23
|
-
case 'name':
|
|
24
|
-
return a.name.localeCompare(b.name);
|
|
25
|
-
case 'category': {
|
|
26
|
-
const catA = a.categories[0] ?? '';
|
|
27
|
-
const catB = b.categories[0] ?? '';
|
|
28
|
-
return catA.localeCompare(catB);
|
|
29
|
-
}
|
|
30
|
-
case 'auth': {
|
|
31
|
-
const authA = a.auth ?? '';
|
|
32
|
-
const authB = b.auth ?? '';
|
|
33
|
-
return authA.localeCompare(authB);
|
|
34
|
-
}
|
|
35
|
-
default:
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
return results;
|
|
41
|
-
}
|
|
42
|
-
export function getCategories(apis) {
|
|
43
|
-
const map = new Map();
|
|
44
|
-
for (const api of apis) {
|
|
45
|
-
for (const cat of api.categories) {
|
|
46
|
-
map.set(cat, (map.get(cat) ?? 0) + 1);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return map;
|
|
50
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/search.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export function search(apis, options) {
|
|
2
|
-
let results = apis;
|
|
3
|
-
if (options.query) {
|
|
4
|
-
const q = options.query.toLowerCase();
|
|
5
|
-
results = results.filter((api) => api.name.toLowerCase().includes(q) || api.description?.toLowerCase().includes(q));
|
|
6
|
-
}
|
|
7
|
-
if (options.category) {
|
|
8
|
-
const cat = options.category.toLowerCase();
|
|
9
|
-
results = results.filter((api) => api.categories.some((c) => c.toLowerCase().includes(cat)));
|
|
10
|
-
}
|
|
11
|
-
if (options.auth) {
|
|
12
|
-
const auth = options.auth.toLowerCase();
|
|
13
|
-
results = results.filter((api) => {
|
|
14
|
-
if (auth === 'no') {
|
|
15
|
-
return !api.auth;
|
|
16
|
-
}
|
|
17
|
-
return api.auth?.toLowerCase().includes(auth);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
if (options.sort) {
|
|
21
|
-
results = [...results].sort((a, b) => {
|
|
22
|
-
switch (options.sort) {
|
|
23
|
-
case 'name':
|
|
24
|
-
return a.name.localeCompare(b.name);
|
|
25
|
-
case 'category': {
|
|
26
|
-
const catA = a.categories[0] ?? '';
|
|
27
|
-
const catB = b.categories[0] ?? '';
|
|
28
|
-
return catA.localeCompare(catB);
|
|
29
|
-
}
|
|
30
|
-
case 'auth': {
|
|
31
|
-
const authA = a.auth ?? '';
|
|
32
|
-
const authB = b.auth ?? '';
|
|
33
|
-
return authA.localeCompare(authB);
|
|
34
|
-
}
|
|
35
|
-
default:
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
return results;
|
|
41
|
-
}
|
|
42
|
-
export function getCategories(apis) {
|
|
43
|
-
const map = new Map();
|
|
44
|
-
for (const api of apis) {
|
|
45
|
-
for (const cat of api.categories) {
|
|
46
|
-
map.set(cat, (map.get(cat) ?? 0) + 1);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return map;
|
|
50
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|