@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
package/convex/http.ts
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import { httpRouter } from "convex/server";
|
|
2
|
+
import { httpAction } from "./_generated/server";
|
|
3
|
+
import { api, internal } from "./_generated/api";
|
|
4
|
+
|
|
5
|
+
const http = httpRouter();
|
|
6
|
+
|
|
7
|
+
// Provider catalog
|
|
8
|
+
const PROVIDERS = {
|
|
9
|
+
"46elks": {
|
|
10
|
+
name: "46elks",
|
|
11
|
+
description: "SMS API for EU/Nordics. GDPR compliant.",
|
|
12
|
+
category: "sms",
|
|
13
|
+
pricing: "~$0.035/SMS",
|
|
14
|
+
regions: ["EU", "Nordic"],
|
|
15
|
+
tags: ["sms", "eu", "gdpr", "nordic"],
|
|
16
|
+
},
|
|
17
|
+
twilio: {
|
|
18
|
+
name: "Twilio",
|
|
19
|
+
description: "SMS and Voice API. Global coverage.",
|
|
20
|
+
category: "sms",
|
|
21
|
+
pricing: "~$0.04/SMS, ~$0.01/min voice",
|
|
22
|
+
regions: ["Global"],
|
|
23
|
+
tags: ["sms", "voice", "global"],
|
|
24
|
+
},
|
|
25
|
+
resend: {
|
|
26
|
+
name: "Resend",
|
|
27
|
+
description: "Modern email API. Developer-friendly.",
|
|
28
|
+
category: "email",
|
|
29
|
+
pricing: "~$0.001/email",
|
|
30
|
+
regions: ["Global"],
|
|
31
|
+
tags: ["email", "transactional"],
|
|
32
|
+
},
|
|
33
|
+
brave_search: {
|
|
34
|
+
name: "Brave Search",
|
|
35
|
+
description: "Privacy-focused web search API.",
|
|
36
|
+
category: "search",
|
|
37
|
+
pricing: "~$0.005/search",
|
|
38
|
+
regions: ["Global"],
|
|
39
|
+
tags: ["search", "web", "privacy"],
|
|
40
|
+
},
|
|
41
|
+
openrouter: {
|
|
42
|
+
name: "OpenRouter",
|
|
43
|
+
description: "Multi-model LLM API. Access GPT, Claude, Llama, etc.",
|
|
44
|
+
category: "llm",
|
|
45
|
+
pricing: "Varies by model",
|
|
46
|
+
regions: ["Global"],
|
|
47
|
+
tags: ["llm", "ai", "gpt", "claude"],
|
|
48
|
+
},
|
|
49
|
+
elevenlabs: {
|
|
50
|
+
name: "ElevenLabs",
|
|
51
|
+
description: "Text-to-speech API. High quality voices.",
|
|
52
|
+
category: "tts",
|
|
53
|
+
pricing: "~$0.0003/char",
|
|
54
|
+
regions: ["Global"],
|
|
55
|
+
tags: ["tts", "voice", "audio"],
|
|
56
|
+
},
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
// CORS headers
|
|
60
|
+
const corsHeaders = {
|
|
61
|
+
"Access-Control-Allow-Origin": "*",
|
|
62
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
63
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Helper for JSON responses
|
|
67
|
+
function jsonResponse(data: unknown, status = 200) {
|
|
68
|
+
return new Response(JSON.stringify(data), {
|
|
69
|
+
status,
|
|
70
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// OPTIONS handler for CORS
|
|
75
|
+
http.route({
|
|
76
|
+
path: "/api/discover",
|
|
77
|
+
method: "OPTIONS",
|
|
78
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
http.route({
|
|
82
|
+
path: "/api/details",
|
|
83
|
+
method: "OPTIONS",
|
|
84
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
http.route({
|
|
88
|
+
path: "/api/balance",
|
|
89
|
+
method: "OPTIONS",
|
|
90
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
http.route({
|
|
94
|
+
path: "/api/purchase",
|
|
95
|
+
method: "OPTIONS",
|
|
96
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
http.route({
|
|
100
|
+
path: "/admin/grant-credits",
|
|
101
|
+
method: "OPTIONS",
|
|
102
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Discover APIs
|
|
106
|
+
http.route({
|
|
107
|
+
path: "/api/discover",
|
|
108
|
+
method: "POST",
|
|
109
|
+
handler: httpAction(async (ctx, request) => {
|
|
110
|
+
try {
|
|
111
|
+
const body = await request.json();
|
|
112
|
+
const query = (body.query || "").toLowerCase();
|
|
113
|
+
|
|
114
|
+
const results = Object.entries(PROVIDERS)
|
|
115
|
+
.filter(([id, provider]) => {
|
|
116
|
+
if (!query) return true;
|
|
117
|
+
return (
|
|
118
|
+
provider.name.toLowerCase().includes(query) ||
|
|
119
|
+
provider.description.toLowerCase().includes(query) ||
|
|
120
|
+
provider.category.toLowerCase().includes(query) ||
|
|
121
|
+
provider.tags.some((tag) => tag.includes(query))
|
|
122
|
+
);
|
|
123
|
+
})
|
|
124
|
+
.map(([id, provider]) => ({
|
|
125
|
+
providerId: id,
|
|
126
|
+
...provider,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
return jsonResponse({ providers: results, total: results.length });
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return jsonResponse({ error: "Invalid request" }, 400);
|
|
132
|
+
}
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Get provider details
|
|
137
|
+
http.route({
|
|
138
|
+
path: "/api/details",
|
|
139
|
+
method: "POST",
|
|
140
|
+
handler: httpAction(async (ctx, request) => {
|
|
141
|
+
try {
|
|
142
|
+
const body = await request.json();
|
|
143
|
+
const { providerId } = body;
|
|
144
|
+
|
|
145
|
+
if (!providerId) {
|
|
146
|
+
return jsonResponse({ error: "providerId required" }, 400);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
|
|
150
|
+
if (!provider) {
|
|
151
|
+
return jsonResponse({ error: "Provider not found" }, 404);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return jsonResponse({
|
|
155
|
+
providerId,
|
|
156
|
+
...provider,
|
|
157
|
+
creditsPerDollar: getCreditsPerDollar(providerId),
|
|
158
|
+
documentation: `https://apiclaw.com/docs/${providerId}`,
|
|
159
|
+
});
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return jsonResponse({ error: "Invalid request" }, 400);
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Check balance
|
|
167
|
+
http.route({
|
|
168
|
+
path: "/api/balance",
|
|
169
|
+
method: "GET",
|
|
170
|
+
handler: httpAction(async (ctx, request) => {
|
|
171
|
+
const url = new URL(request.url);
|
|
172
|
+
const agentId = url.searchParams.get("agentId");
|
|
173
|
+
|
|
174
|
+
if (!agentId) {
|
|
175
|
+
return jsonResponse({ error: "agentId required" }, 400);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const credits = await ctx.runQuery(api.credits.getAgentCredits, { agentId });
|
|
179
|
+
|
|
180
|
+
if (!credits) {
|
|
181
|
+
return jsonResponse({
|
|
182
|
+
agentId,
|
|
183
|
+
balanceUsd: 0,
|
|
184
|
+
currency: "USD",
|
|
185
|
+
message: "No account found. Top up to get started!",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return jsonResponse({
|
|
190
|
+
agentId: credits.agentId,
|
|
191
|
+
balanceUsd: credits.balanceUsd,
|
|
192
|
+
currency: credits.currency,
|
|
193
|
+
});
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Purchase API access
|
|
198
|
+
http.route({
|
|
199
|
+
path: "/api/purchase",
|
|
200
|
+
method: "POST",
|
|
201
|
+
handler: httpAction(async (ctx, request) => {
|
|
202
|
+
try {
|
|
203
|
+
const body = await request.json();
|
|
204
|
+
const { agentId, providerId, amountUsd } = body;
|
|
205
|
+
|
|
206
|
+
if (!agentId || !providerId || !amountUsd) {
|
|
207
|
+
return jsonResponse(
|
|
208
|
+
{ error: "agentId, providerId, and amountUsd required" },
|
|
209
|
+
400
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (amountUsd < 1 || amountUsd > 1000) {
|
|
214
|
+
return jsonResponse(
|
|
215
|
+
{ error: "amountUsd must be between 1 and 1000" },
|
|
216
|
+
400
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
|
|
221
|
+
if (!provider) {
|
|
222
|
+
return jsonResponse({ error: "Provider not found" }, 404);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check balance first
|
|
226
|
+
const credits = await ctx.runQuery(api.credits.getAgentCredits, { agentId });
|
|
227
|
+
if (!credits || credits.balanceUsd < amountUsd) {
|
|
228
|
+
return jsonResponse(
|
|
229
|
+
{
|
|
230
|
+
error: "Insufficient balance",
|
|
231
|
+
currentBalance: credits?.balanceUsd || 0,
|
|
232
|
+
required: amountUsd,
|
|
233
|
+
},
|
|
234
|
+
402
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Execute purchase
|
|
239
|
+
const purchase = await ctx.runMutation(api.purchases.purchaseAccess, {
|
|
240
|
+
agentId,
|
|
241
|
+
providerId,
|
|
242
|
+
amountUsd,
|
|
243
|
+
credentials: generateCredentials(providerId),
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (!purchase) {
|
|
247
|
+
return jsonResponse({ error: "Purchase failed" }, 500);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return jsonResponse({
|
|
251
|
+
success: true,
|
|
252
|
+
purchase: {
|
|
253
|
+
id: purchase._id,
|
|
254
|
+
providerId: purchase.providerId,
|
|
255
|
+
amountUsd: purchase.amountUsd,
|
|
256
|
+
creditsGranted: purchase.creditsGranted,
|
|
257
|
+
status: purchase.status,
|
|
258
|
+
},
|
|
259
|
+
message: `Successfully purchased $${amountUsd} of ${provider.name} credits`,
|
|
260
|
+
});
|
|
261
|
+
} catch (e: any) {
|
|
262
|
+
return jsonResponse({ error: e.message || "Purchase failed" }, 400);
|
|
263
|
+
}
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Admin: Grant credits
|
|
268
|
+
http.route({
|
|
269
|
+
path: "/admin/grant-credits",
|
|
270
|
+
method: "POST",
|
|
271
|
+
handler: httpAction(async (ctx, request) => {
|
|
272
|
+
try {
|
|
273
|
+
const body = await request.json();
|
|
274
|
+
const { agentId, amount, reason } = body;
|
|
275
|
+
|
|
276
|
+
if (!agentId || !amount) {
|
|
277
|
+
return jsonResponse({ error: "agentId and amount required" }, 400);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// TODO: Add admin auth check here
|
|
281
|
+
// For now, allow grants (this is for Hivr integration)
|
|
282
|
+
|
|
283
|
+
const result = await ctx.runMutation(api.credits.addCredits, {
|
|
284
|
+
agentId,
|
|
285
|
+
amountUsd: amount,
|
|
286
|
+
source: reason || "admin_grant",
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return jsonResponse({
|
|
290
|
+
success: true,
|
|
291
|
+
agentId,
|
|
292
|
+
credited: amount,
|
|
293
|
+
newBalance: result?.balanceUsd,
|
|
294
|
+
reason,
|
|
295
|
+
});
|
|
296
|
+
} catch (e: any) {
|
|
297
|
+
return jsonResponse({ error: e.message || "Grant failed" }, 400);
|
|
298
|
+
}
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Helper functions
|
|
303
|
+
function getCreditsPerDollar(providerId: string): number {
|
|
304
|
+
const rates: Record<string, number> = {
|
|
305
|
+
"46elks": 30,
|
|
306
|
+
twilio: 25,
|
|
307
|
+
resend: 1000,
|
|
308
|
+
brave_search: 200,
|
|
309
|
+
openrouter: 100,
|
|
310
|
+
elevenlabs: 3333,
|
|
311
|
+
};
|
|
312
|
+
return rates[providerId] || 100;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function generateCredentials(providerId: string): object {
|
|
316
|
+
// In production, this would generate or retrieve actual API keys
|
|
317
|
+
// For now, return placeholder indicating how to use
|
|
318
|
+
return {
|
|
319
|
+
type: "apiclaw_proxy",
|
|
320
|
+
endpoint: `https://brilliant-puffin-712.convex.site/proxy/${providerId}`,
|
|
321
|
+
note: "Use APIClaw proxy endpoint. Credentials managed automatically.",
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export default http;
|
|
326
|
+
|
|
327
|
+
// ==============================================
|
|
328
|
+
// INSTANT CONNECT PROXY ENDPOINTS
|
|
329
|
+
// ==============================================
|
|
330
|
+
|
|
331
|
+
// OpenRouter proxy
|
|
332
|
+
http.route({
|
|
333
|
+
path: "/proxy/openrouter",
|
|
334
|
+
method: "POST",
|
|
335
|
+
handler: httpAction(async (ctx, request) => {
|
|
336
|
+
const OPENROUTER_KEY = process.env.OPENROUTER_API_KEY;
|
|
337
|
+
if (!OPENROUTER_KEY) {
|
|
338
|
+
return jsonResponse({ error: "OpenRouter not configured" }, 500);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const body = await request.json();
|
|
343
|
+
|
|
344
|
+
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
345
|
+
method: "POST",
|
|
346
|
+
headers: {
|
|
347
|
+
"Authorization": `Bearer ${OPENROUTER_KEY}`,
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
"HTTP-Referer": "https://apiclaw.nordsym.com",
|
|
350
|
+
"X-Title": "APIClaw",
|
|
351
|
+
},
|
|
352
|
+
body: JSON.stringify(body),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const data = await response.json();
|
|
356
|
+
return jsonResponse(data, response.status);
|
|
357
|
+
} catch (e: any) {
|
|
358
|
+
return jsonResponse({ error: e.message }, 500);
|
|
359
|
+
}
|
|
360
|
+
}),
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Brave Search proxy
|
|
364
|
+
http.route({
|
|
365
|
+
path: "/proxy/brave_search",
|
|
366
|
+
method: "POST",
|
|
367
|
+
handler: httpAction(async (ctx, request) => {
|
|
368
|
+
const BRAVE_KEY = process.env.BRAVE_API_KEY;
|
|
369
|
+
if (!BRAVE_KEY) {
|
|
370
|
+
return jsonResponse({ error: "Brave Search not configured" }, 500);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const body = await request.json();
|
|
375
|
+
const { query, count = 10 } = body;
|
|
376
|
+
|
|
377
|
+
const url = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
378
|
+
url.searchParams.set("q", query);
|
|
379
|
+
url.searchParams.set("count", String(count));
|
|
380
|
+
|
|
381
|
+
const response = await fetch(url.toString(), {
|
|
382
|
+
headers: { "X-Subscription-Token": BRAVE_KEY },
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const data = await response.json();
|
|
386
|
+
return jsonResponse(data, response.status);
|
|
387
|
+
} catch (e: any) {
|
|
388
|
+
return jsonResponse({ error: e.message }, 500);
|
|
389
|
+
}
|
|
390
|
+
}),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Resend email proxy
|
|
394
|
+
http.route({
|
|
395
|
+
path: "/proxy/resend",
|
|
396
|
+
method: "POST",
|
|
397
|
+
handler: httpAction(async (ctx, request) => {
|
|
398
|
+
const RESEND_KEY = process.env.RESEND_API_KEY;
|
|
399
|
+
if (!RESEND_KEY) {
|
|
400
|
+
return jsonResponse({ error: "Resend not configured" }, 500);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const body = await request.json();
|
|
405
|
+
|
|
406
|
+
const response = await fetch("https://api.resend.com/emails", {
|
|
407
|
+
method: "POST",
|
|
408
|
+
headers: {
|
|
409
|
+
"Authorization": `Bearer ${RESEND_KEY}`,
|
|
410
|
+
"Content-Type": "application/json",
|
|
411
|
+
},
|
|
412
|
+
body: JSON.stringify(body),
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const data = await response.json();
|
|
416
|
+
return jsonResponse(data, response.status);
|
|
417
|
+
} catch (e: any) {
|
|
418
|
+
return jsonResponse({ error: e.message }, 500);
|
|
419
|
+
}
|
|
420
|
+
}),
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ElevenLabs TTS proxy
|
|
424
|
+
http.route({
|
|
425
|
+
path: "/proxy/elevenlabs",
|
|
426
|
+
method: "POST",
|
|
427
|
+
handler: httpAction(async (ctx, request) => {
|
|
428
|
+
const ELEVENLABS_KEY = process.env.ELEVENLABS_API_KEY;
|
|
429
|
+
if (!ELEVENLABS_KEY) {
|
|
430
|
+
return jsonResponse({ error: "ElevenLabs not configured" }, 500);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const body = await request.json();
|
|
435
|
+
const { text, voice_id = "21m00Tcm4TlvDq8ikWAM" } = body;
|
|
436
|
+
|
|
437
|
+
const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voice_id}`, {
|
|
438
|
+
method: "POST",
|
|
439
|
+
headers: {
|
|
440
|
+
"xi-api-key": ELEVENLABS_KEY,
|
|
441
|
+
"Content-Type": "application/json",
|
|
442
|
+
},
|
|
443
|
+
body: JSON.stringify({
|
|
444
|
+
text,
|
|
445
|
+
model_id: "eleven_turbo_v2",
|
|
446
|
+
}),
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (!response.ok) {
|
|
450
|
+
const error = await response.text();
|
|
451
|
+
return jsonResponse({ error }, response.status);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Return audio as base64
|
|
455
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
456
|
+
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
457
|
+
|
|
458
|
+
return jsonResponse({
|
|
459
|
+
audio_base64: base64,
|
|
460
|
+
content_type: "audio/mpeg",
|
|
461
|
+
});
|
|
462
|
+
} catch (e: any) {
|
|
463
|
+
return jsonResponse({ error: e.message }, 500);
|
|
464
|
+
}
|
|
465
|
+
}),
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
http.route({
|
|
469
|
+
path: "/proxy/openrouter",
|
|
470
|
+
method: "OPTIONS",
|
|
471
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
http.route({
|
|
475
|
+
path: "/proxy/brave_search",
|
|
476
|
+
method: "OPTIONS",
|
|
477
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
http.route({
|
|
481
|
+
path: "/proxy/resend",
|
|
482
|
+
method: "OPTIONS",
|
|
483
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
http.route({
|
|
487
|
+
path: "/proxy/elevenlabs",
|
|
488
|
+
method: "OPTIONS",
|
|
489
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// 46elks SMS proxy
|
|
493
|
+
http.route({
|
|
494
|
+
path: "/proxy/46elks",
|
|
495
|
+
method: "POST",
|
|
496
|
+
handler: httpAction(async (ctx, request) => {
|
|
497
|
+
const ELKS_USER = process.env.ELKS_API_USER;
|
|
498
|
+
const ELKS_PASS = process.env.ELKS_API_PASSWORD;
|
|
499
|
+
if (!ELKS_USER || !ELKS_PASS) {
|
|
500
|
+
return jsonResponse({ error: "46elks not configured" }, 500);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
const body = await request.json();
|
|
505
|
+
const { to, message, from = "APIClaw" } = body;
|
|
506
|
+
|
|
507
|
+
const auth = btoa(`${ELKS_USER}:${ELKS_PASS}`);
|
|
508
|
+
|
|
509
|
+
const response = await fetch("https://api.46elks.com/a1/sms", {
|
|
510
|
+
method: "POST",
|
|
511
|
+
headers: {
|
|
512
|
+
"Authorization": `Basic ${auth}`,
|
|
513
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
514
|
+
},
|
|
515
|
+
body: new URLSearchParams({ from, to, message }),
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const data = await response.json();
|
|
519
|
+
return jsonResponse(data, response.status);
|
|
520
|
+
} catch (e: any) {
|
|
521
|
+
return jsonResponse({ error: e.message }, 500);
|
|
522
|
+
}
|
|
523
|
+
}),
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Twilio SMS proxy
|
|
527
|
+
http.route({
|
|
528
|
+
path: "/proxy/twilio",
|
|
529
|
+
method: "POST",
|
|
530
|
+
handler: httpAction(async (ctx, request) => {
|
|
531
|
+
const TWILIO_SID = process.env.TWILIO_ACCOUNT_SID;
|
|
532
|
+
const TWILIO_TOKEN = process.env.TWILIO_AUTH_TOKEN;
|
|
533
|
+
if (!TWILIO_SID || !TWILIO_TOKEN) {
|
|
534
|
+
return jsonResponse({ error: "Twilio not configured" }, 500);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const body = await request.json();
|
|
539
|
+
const { to, message, from } = body;
|
|
540
|
+
|
|
541
|
+
if (!from) {
|
|
542
|
+
return jsonResponse({ error: "Twilio requires 'from' number" }, 400);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const auth = btoa(`${TWILIO_SID}:${TWILIO_TOKEN}`);
|
|
546
|
+
|
|
547
|
+
const response = await fetch(
|
|
548
|
+
`https://api.twilio.com/2010-04-01/Accounts/${TWILIO_SID}/Messages.json`,
|
|
549
|
+
{
|
|
550
|
+
method: "POST",
|
|
551
|
+
headers: {
|
|
552
|
+
"Authorization": `Basic ${auth}`,
|
|
553
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
554
|
+
},
|
|
555
|
+
body: new URLSearchParams({ To: to, From: from, Body: message }),
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const data = await response.json();
|
|
560
|
+
return jsonResponse(data, response.status);
|
|
561
|
+
} catch (e: any) {
|
|
562
|
+
return jsonResponse({ error: e.message }, 500);
|
|
563
|
+
}
|
|
564
|
+
}),
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// CORS for new endpoints
|
|
568
|
+
http.route({
|
|
569
|
+
path: "/proxy/46elks",
|
|
570
|
+
method: "OPTIONS",
|
|
571
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
http.route({
|
|
575
|
+
path: "/proxy/twilio",
|
|
576
|
+
method: "OPTIONS",
|
|
577
|
+
handler: httpAction(async () => new Response(null, { headers: corsHeaders })),
|
|
578
|
+
});
|