@nordsym/apiclaw 1.0.0 → 1.1.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/AGENTS.md +74 -0
- package/HEARTBEAT.md +4 -0
- package/IDENTITY.md +22 -0
- package/README.md +193 -202
- package/SOUL.md +36 -0
- package/STATUS.md +237 -0
- package/TOOLS.md +36 -0
- package/USER.md +17 -0
- package/{backend/convex → convex}/_generated/api.d.ts +12 -6
- package/convex/analytics.ts +90 -0
- package/convex/credits.ts +211 -0
- package/convex/http.ts +578 -0
- package/convex/providers.ts +516 -0
- package/convex/purchases.ts +183 -0
- package/convex/ratelimit.ts +104 -0
- package/convex/schema.ts +220 -0
- package/convex/telemetry.ts +81 -0
- package/convex.json +3 -0
- package/dist/credentials.d.ts +19 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +158 -0
- package/dist/credentials.js.map +1 -0
- package/dist/credits.d.ts +14 -11
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +151 -99
- package/dist/credits.js.map +1 -1
- package/dist/discovery.d.ts +7 -16
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +33 -40
- package/dist/discovery.js.map +1 -1
- package/dist/execute.d.ts +19 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +285 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.js +175 -31
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +6 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +19 -0
- package/dist/proxy.js.map +1 -0
- package/dist/registry/apis.json +95362 -202
- package/dist/registry/apis_expanded.json +100853 -0
- package/dist/stripe.d.ts +68 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +196 -0
- package/dist/stripe.js.map +1 -0
- package/dist/telemetry.d.ts +28 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +50 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/test.d.ts +3 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +105 -75
- package/dist/test.js.map +1 -1
- package/dist/types.d.ts +0 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook.d.ts +2 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +90 -0
- package/dist/webhook.js.map +1 -0
- package/landing/DESIGN.md +343 -0
- package/landing/package-lock.json +1196 -7
- package/landing/package.json +5 -1
- package/landing/public/android-chrome-192x192.png +0 -0
- package/landing/public/android-chrome-512x512.png +0 -0
- package/landing/public/apple-touch-icon.png +0 -0
- package/landing/public/demo.gif +0 -0
- package/landing/public/demo.mp4 +0 -0
- package/landing/public/favicon-16x16.png +0 -0
- package/landing/public/favicon-32x32.png +0 -0
- package/landing/public/favicon.ico +0 -0
- package/landing/public/favicon.svg +3 -0
- package/landing/public/icon.svg +47 -0
- package/landing/public/logo-mono.svg +37 -0
- package/landing/public/logo-simple.svg +45 -0
- package/landing/public/logo.svg +84 -0
- package/landing/public/og-template.html +184 -0
- package/landing/public/site.webmanifest +31 -0
- package/landing/scripts/generate-assets.js +284 -0
- package/landing/scripts/generate-pngs.js +48 -0
- package/landing/scripts/generate-stats.js +42 -0
- package/landing/src/app/admin/page.tsx +348 -0
- package/landing/src/app/api/auth/magic-link/route.ts +73 -0
- package/landing/src/app/api/auth/session/route.ts +38 -0
- package/landing/src/app/api/auth/verify/route.ts +43 -0
- package/landing/src/app/api/og/route.tsx +84 -0
- package/landing/src/app/globals.css +439 -100
- package/landing/src/app/layout.tsx +37 -7
- package/landing/src/app/page.tsx +627 -552
- package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
- package/landing/src/app/providers/dashboard/page.tsx +589 -0
- package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
- package/landing/src/app/providers/layout.tsx +14 -0
- package/landing/src/app/providers/page.tsx +402 -0
- package/landing/src/app/providers/register/page.tsx +670 -0
- package/landing/src/components/ProviderDashboard.tsx +794 -0
- package/landing/src/hooks/useDashboardData.ts +99 -0
- package/landing/src/lib/apis.json +116054 -0
- package/landing/src/lib/convex-client.ts +106 -0
- package/landing/src/lib/mock-data.ts +285 -0
- package/landing/src/lib/stats.json +6 -0
- package/landing/tailwind.config.ts +12 -11
- package/landing/tsconfig.tsbuildinfo +1 -0
- package/package.json +21 -20
- package/scripts/SYMBOT-FIX.md +238 -0
- package/scripts/demo-simulation.py +177 -0
- package/scripts/expand-more.py +502 -0
- package/scripts/expand-registry.py +434 -0
- package/scripts/history-sanitizer.ts +272 -0
- package/scripts/mass-scrape.py +1308 -0
- package/scripts/sync-and-deploy.sh +36 -0
- package/src/credentials.ts +177 -0
- package/src/credits.ts +190 -122
- package/src/discovery.ts +45 -58
- package/src/execute.ts +350 -0
- package/src/index.ts +184 -32
- package/src/proxy.ts +24 -0
- package/src/registry/apis.json +95362 -202
- package/src/registry/apis_expanded.json +100853 -0
- package/src/stripe.ts +243 -0
- package/src/telemetry.ts +71 -0
- package/src/test.ts +127 -89
- package/src/types.ts +0 -34
- package/src/webhook.ts +107 -0
- package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
- package/BRIEFING.md +0 -30
- package/backend/convex/apiKeys.ts +0 -75
- package/backend/convex/purchases.ts +0 -74
- package/backend/convex/schema.ts +0 -45
- package/backend/convex/transactions.ts +0 -57
- package/backend/convex/users.ts +0 -94
- package/backend/package-lock.json +0 -521
- package/backend/package.json +0 -15
- package/dist/registry/parse_apis.py +0 -146
- package/dist/revenuecat.d.ts +0 -61
- package/dist/revenuecat.d.ts.map +0 -1
- package/dist/revenuecat.js +0 -166
- package/dist/revenuecat.js.map +0 -1
- package/dist/webhooks/revenuecat.d.ts +0 -48
- package/dist/webhooks/revenuecat.d.ts.map +0 -1
- package/dist/webhooks/revenuecat.js +0 -119
- package/dist/webhooks/revenuecat.js.map +0 -1
- package/docs/revenuecat-setup.md +0 -89
- package/landing/src/app/api/keys/route.ts +0 -71
- package/landing/src/app/api/log/route.ts +0 -37
- package/landing/src/app/api/stats/route.ts +0 -37
- package/landing/src/app/page.tsx.bak +0 -567
- package/landing/src/components/AddKeyModal.tsx +0 -159
- package/newsletter-template.html +0 -71
- package/outreach/OUTREACH-SYSTEM.md +0 -211
- package/outreach/email-template.html +0 -179
- package/outreach/targets.md +0 -133
- package/src/registry/parse_apis.py +0 -146
- package/src/revenuecat.ts +0 -239
- package/src/webhooks/revenuecat.ts +0 -187
- /package/{backend/convex → convex}/README.md +0 -0
- /package/{backend/convex → convex}/_generated/api.js +0 -0
- /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.js +0 -0
- /package/{backend/convex → convex}/tsconfig.json +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate PNG assets from SVG for APIClaw
|
|
4
|
+
* - Favicons (16x16, 32x32, 180x180)
|
|
5
|
+
* - OG Image (1200x630)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const publicDir = path.join(__dirname, '..', 'public');
|
|
13
|
+
|
|
14
|
+
// Create OG image HTML template
|
|
15
|
+
const ogImageHtml = `
|
|
16
|
+
<!DOCTYPE html>
|
|
17
|
+
<html>
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8">
|
|
20
|
+
<style>
|
|
21
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap');
|
|
22
|
+
|
|
23
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
width: 1200px;
|
|
27
|
+
height: 630px;
|
|
28
|
+
background: linear-gradient(135deg, #0a0a0a 0%, #171717 100%);
|
|
29
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
padding: 80px;
|
|
34
|
+
position: relative;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Background pattern */
|
|
39
|
+
.pattern {
|
|
40
|
+
position: absolute;
|
|
41
|
+
top: 0;
|
|
42
|
+
left: 0;
|
|
43
|
+
right: 0;
|
|
44
|
+
bottom: 0;
|
|
45
|
+
background-image:
|
|
46
|
+
radial-gradient(circle at 20% 80%, rgba(239, 68, 68, 0.15) 0%, transparent 50%),
|
|
47
|
+
radial-gradient(circle at 80% 20%, rgba(239, 68, 68, 0.1) 0%, transparent 40%);
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Grid lines */
|
|
52
|
+
.grid {
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 0;
|
|
55
|
+
left: 0;
|
|
56
|
+
right: 0;
|
|
57
|
+
bottom: 0;
|
|
58
|
+
background-image:
|
|
59
|
+
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
|
|
60
|
+
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
|
|
61
|
+
background-size: 60px 60px;
|
|
62
|
+
pointer-events: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.content {
|
|
66
|
+
position: relative;
|
|
67
|
+
z-index: 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.logo-row {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 24px;
|
|
74
|
+
margin-bottom: 40px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.logo {
|
|
78
|
+
width: 80px;
|
|
79
|
+
height: 80px;
|
|
80
|
+
background: linear-gradient(135deg, #f87171 0%, #dc2626 100%);
|
|
81
|
+
border-radius: 20px;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
font-size: 48px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.brand {
|
|
89
|
+
font-size: 48px;
|
|
90
|
+
font-weight: 800;
|
|
91
|
+
color: #fafafa;
|
|
92
|
+
letter-spacing: -0.02em;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.tagline {
|
|
96
|
+
font-size: 56px;
|
|
97
|
+
font-weight: 800;
|
|
98
|
+
line-height: 1.2;
|
|
99
|
+
letter-spacing: -0.03em;
|
|
100
|
+
margin-bottom: 32px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.tagline .gradient {
|
|
104
|
+
background: linear-gradient(135deg, #f87171 0%, #ef4444 50%, #fca5a5 100%);
|
|
105
|
+
-webkit-background-clip: text;
|
|
106
|
+
-webkit-text-fill-color: transparent;
|
|
107
|
+
background-clip: text;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.tagline .white {
|
|
111
|
+
color: #fafafa;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.subtitle {
|
|
115
|
+
font-size: 24px;
|
|
116
|
+
color: #a3a3a3;
|
|
117
|
+
max-width: 700px;
|
|
118
|
+
line-height: 1.5;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.footer {
|
|
122
|
+
position: absolute;
|
|
123
|
+
bottom: 60px;
|
|
124
|
+
left: 80px;
|
|
125
|
+
right: 80px;
|
|
126
|
+
display: flex;
|
|
127
|
+
justify-content: space-between;
|
|
128
|
+
align-items: center;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.stats {
|
|
132
|
+
display: flex;
|
|
133
|
+
gap: 48px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.stat {
|
|
137
|
+
text-align: left;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.stat-value {
|
|
141
|
+
font-size: 32px;
|
|
142
|
+
font-weight: 800;
|
|
143
|
+
color: #ef4444;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.stat-label {
|
|
147
|
+
font-size: 14px;
|
|
148
|
+
color: #737373;
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
letter-spacing: 0.1em;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.url {
|
|
154
|
+
font-size: 20px;
|
|
155
|
+
color: #525252;
|
|
156
|
+
font-weight: 500;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
159
|
+
</head>
|
|
160
|
+
<body>
|
|
161
|
+
<div class="pattern"></div>
|
|
162
|
+
<div class="grid"></div>
|
|
163
|
+
|
|
164
|
+
<div class="content">
|
|
165
|
+
<div class="logo-row">
|
|
166
|
+
<div class="logo">🦞</div>
|
|
167
|
+
<div class="brand">APIClaw</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<h1 class="tagline">
|
|
171
|
+
<span class="gradient">The API layer</span><br>
|
|
172
|
+
<span class="white">for AI agents</span>
|
|
173
|
+
</h1>
|
|
174
|
+
|
|
175
|
+
<p class="subtitle">
|
|
176
|
+
Agents discover and evaluate APIs via MCP. Structured data. Ranked results. No more googling.
|
|
177
|
+
</p>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="footer">
|
|
181
|
+
<div class="stats">
|
|
182
|
+
<div class="stat">
|
|
183
|
+
<div class="stat-value">1,400+</div>
|
|
184
|
+
<div class="stat-label">APIs</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="stat">
|
|
187
|
+
<div class="stat-value">52</div>
|
|
188
|
+
<div class="stat-label">Categories</div>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="stat">
|
|
191
|
+
<div class="stat-value">MCP</div>
|
|
192
|
+
<div class="stat-label">Native</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="url">apiclaw.com</div>
|
|
196
|
+
</div>
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
// Write OG image HTML
|
|
202
|
+
fs.writeFileSync(path.join(publicDir, 'og-template.html'), ogImageHtml);
|
|
203
|
+
console.log('✓ Created OG image template');
|
|
204
|
+
|
|
205
|
+
// Try to generate PNGs using various methods
|
|
206
|
+
async function generateAssets() {
|
|
207
|
+
// Check for available tools
|
|
208
|
+
const hasRsvgConvert = (() => {
|
|
209
|
+
try {
|
|
210
|
+
execSync('which rsvg-convert', { stdio: 'pipe' });
|
|
211
|
+
return true;
|
|
212
|
+
} catch { return false; }
|
|
213
|
+
})();
|
|
214
|
+
|
|
215
|
+
const hasConvert = (() => {
|
|
216
|
+
try {
|
|
217
|
+
execSync('which convert', { stdio: 'pipe' });
|
|
218
|
+
return true;
|
|
219
|
+
} catch { return false; }
|
|
220
|
+
})();
|
|
221
|
+
|
|
222
|
+
const hasSips = (() => {
|
|
223
|
+
try {
|
|
224
|
+
execSync('which sips', { stdio: 'pipe' });
|
|
225
|
+
return true;
|
|
226
|
+
} catch { return false; }
|
|
227
|
+
})();
|
|
228
|
+
|
|
229
|
+
console.log(`Available tools: rsvg-convert=${hasRsvgConvert}, convert=${hasConvert}, sips=${hasSips}`);
|
|
230
|
+
|
|
231
|
+
// Generate favicons from icon.svg
|
|
232
|
+
const iconSvg = path.join(publicDir, 'icon.svg');
|
|
233
|
+
|
|
234
|
+
const sizes = [
|
|
235
|
+
{ size: 16, name: 'favicon-16x16.png' },
|
|
236
|
+
{ size: 32, name: 'favicon-32x32.png' },
|
|
237
|
+
{ size: 180, name: 'apple-touch-icon.png' },
|
|
238
|
+
{ size: 192, name: 'android-chrome-192x192.png' },
|
|
239
|
+
{ size: 512, name: 'android-chrome-512x512.png' },
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
for (const { size, name } of sizes) {
|
|
243
|
+
const output = path.join(publicDir, name);
|
|
244
|
+
|
|
245
|
+
if (hasRsvgConvert) {
|
|
246
|
+
try {
|
|
247
|
+
execSync(`rsvg-convert -w ${size} -h ${size} "${iconSvg}" -o "${output}"`, { stdio: 'pipe' });
|
|
248
|
+
console.log(`✓ Generated ${name} (${size}x${size})`);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.log(`✗ Failed to generate ${name}: ${e.message}`);
|
|
251
|
+
}
|
|
252
|
+
} else if (hasConvert) {
|
|
253
|
+
try {
|
|
254
|
+
execSync(`convert -background none -resize ${size}x${size} "${iconSvg}" "${output}"`, { stdio: 'pipe' });
|
|
255
|
+
console.log(`✓ Generated ${name} (${size}x${size})`);
|
|
256
|
+
} catch (e) {
|
|
257
|
+
console.log(`✗ Failed to generate ${name}: ${e.message}`);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
console.log(`⚠ Cannot generate ${name} - no SVG converter available`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Generate favicon.ico (multi-size ICO)
|
|
265
|
+
if (hasConvert) {
|
|
266
|
+
try {
|
|
267
|
+
const ico16 = path.join(publicDir, 'favicon-16x16.png');
|
|
268
|
+
const ico32 = path.join(publicDir, 'favicon-32x32.png');
|
|
269
|
+
const icoOut = path.join(publicDir, 'favicon.ico');
|
|
270
|
+
|
|
271
|
+
if (fs.existsSync(ico16) && fs.existsSync(ico32)) {
|
|
272
|
+
execSync(`convert "${ico16}" "${ico32}" "${icoOut}"`, { stdio: 'pipe' });
|
|
273
|
+
console.log('✓ Generated favicon.ico');
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
console.log(`⚠ Could not generate favicon.ico: ${e.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log('\\n📝 Note: For OG image, open og-template.html in browser and screenshot at 1200x630');
|
|
281
|
+
console.log(' Or use: npx playwright screenshot --viewport-size=1200,630 public/og-template.html public/og-image.png');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
generateAssets().catch(console.error);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate PNG assets from SVG using Sharp
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const sharp = require('sharp');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const publicDir = path.join(__dirname, '..', 'public');
|
|
11
|
+
|
|
12
|
+
async function generateFavicons() {
|
|
13
|
+
const iconSvg = fs.readFileSync(path.join(publicDir, 'icon.svg'));
|
|
14
|
+
|
|
15
|
+
const sizes = [
|
|
16
|
+
{ size: 16, name: 'favicon-16x16.png' },
|
|
17
|
+
{ size: 32, name: 'favicon-32x32.png' },
|
|
18
|
+
{ size: 180, name: 'apple-touch-icon.png' },
|
|
19
|
+
{ size: 192, name: 'android-chrome-192x192.png' },
|
|
20
|
+
{ size: 512, name: 'android-chrome-512x512.png' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const { size, name } of sizes) {
|
|
24
|
+
try {
|
|
25
|
+
await sharp(iconSvg)
|
|
26
|
+
.resize(size, size)
|
|
27
|
+
.png()
|
|
28
|
+
.toFile(path.join(publicDir, name));
|
|
29
|
+
console.log(`✓ Generated ${name} (${size}x${size})`);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.log(`✗ Failed ${name}: ${e.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Generate favicon.ico (just use 32x32 as .ico)
|
|
36
|
+
try {
|
|
37
|
+
await sharp(iconSvg)
|
|
38
|
+
.resize(32, 32)
|
|
39
|
+
.toFile(path.join(publicDir, 'favicon.ico'));
|
|
40
|
+
console.log('✓ Generated favicon.ico');
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.log(`✗ Failed favicon.ico: ${e.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
generateFavicons().then(() => {
|
|
47
|
+
console.log('\n✅ Favicon generation complete!');
|
|
48
|
+
}).catch(console.error);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Generate stats at build time from apis.json
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Try local copy first (for Vercel), then parent directory (for local dev)
|
|
6
|
+
const localRegistryPath = path.join(__dirname, '../src/lib/apis.json');
|
|
7
|
+
const parentRegistryPath = path.join(__dirname, '../../src/registry/apis.json');
|
|
8
|
+
const registryPath = fs.existsSync(localRegistryPath) ? localRegistryPath : parentRegistryPath;
|
|
9
|
+
const outputPath = path.join(__dirname, '../src/lib/stats.json');
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
13
|
+
|
|
14
|
+
const categories = [...new Set(registry.apis.map(api => api.category))];
|
|
15
|
+
|
|
16
|
+
const stats = {
|
|
17
|
+
apiCount: registry.count,
|
|
18
|
+
categoryCount: categories.length,
|
|
19
|
+
lastUpdated: registry.lastUpdated || new Date().toISOString().split('T')[0],
|
|
20
|
+
generatedAt: new Date().toISOString()
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Ensure directory exists
|
|
24
|
+
const dir = path.dirname(outputPath);
|
|
25
|
+
if (!fs.existsSync(dir)) {
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fs.writeFileSync(outputPath, JSON.stringify(stats, null, 2));
|
|
30
|
+
console.log('✓ Stats generated:', stats);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('Failed to generate stats:', err);
|
|
33
|
+
// Write fallback stats
|
|
34
|
+
const fallback = {
|
|
35
|
+
apiCount: 4518,
|
|
36
|
+
categoryCount: 93,
|
|
37
|
+
lastUpdated: new Date().toISOString().split('T')[0],
|
|
38
|
+
generatedAt: new Date().toISOString()
|
|
39
|
+
};
|
|
40
|
+
fs.writeFileSync(outputPath, JSON.stringify(fallback, null, 2));
|
|
41
|
+
console.log('✓ Fallback stats written');
|
|
42
|
+
}
|