@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.
Files changed (161) hide show
  1. package/AGENTS.md +74 -0
  2. package/HEARTBEAT.md +4 -0
  3. package/IDENTITY.md +22 -0
  4. package/README.md +193 -202
  5. package/SOUL.md +36 -0
  6. package/STATUS.md +237 -0
  7. package/TOOLS.md +36 -0
  8. package/USER.md +17 -0
  9. package/{backend/convex → convex}/_generated/api.d.ts +12 -6
  10. package/convex/analytics.ts +90 -0
  11. package/convex/credits.ts +211 -0
  12. package/convex/http.ts +578 -0
  13. package/convex/providers.ts +516 -0
  14. package/convex/purchases.ts +183 -0
  15. package/convex/ratelimit.ts +104 -0
  16. package/convex/schema.ts +220 -0
  17. package/convex/telemetry.ts +81 -0
  18. package/convex.json +3 -0
  19. package/dist/credentials.d.ts +19 -0
  20. package/dist/credentials.d.ts.map +1 -0
  21. package/dist/credentials.js +158 -0
  22. package/dist/credentials.js.map +1 -0
  23. package/dist/credits.d.ts +14 -11
  24. package/dist/credits.d.ts.map +1 -1
  25. package/dist/credits.js +151 -99
  26. package/dist/credits.js.map +1 -1
  27. package/dist/discovery.d.ts +7 -16
  28. package/dist/discovery.d.ts.map +1 -1
  29. package/dist/discovery.js +33 -40
  30. package/dist/discovery.js.map +1 -1
  31. package/dist/execute.d.ts +19 -0
  32. package/dist/execute.d.ts.map +1 -0
  33. package/dist/execute.js +285 -0
  34. package/dist/execute.js.map +1 -0
  35. package/dist/index.js +175 -31
  36. package/dist/index.js.map +1 -1
  37. package/dist/proxy.d.ts +6 -0
  38. package/dist/proxy.d.ts.map +1 -0
  39. package/dist/proxy.js +19 -0
  40. package/dist/proxy.js.map +1 -0
  41. package/dist/registry/apis.json +95362 -202
  42. package/dist/registry/apis_expanded.json +100853 -0
  43. package/dist/stripe.d.ts +68 -0
  44. package/dist/stripe.d.ts.map +1 -0
  45. package/dist/stripe.js +196 -0
  46. package/dist/stripe.js.map +1 -0
  47. package/dist/telemetry.d.ts +28 -0
  48. package/dist/telemetry.d.ts.map +1 -0
  49. package/dist/telemetry.js +50 -0
  50. package/dist/telemetry.js.map +1 -0
  51. package/dist/test.d.ts +3 -2
  52. package/dist/test.d.ts.map +1 -1
  53. package/dist/test.js +105 -75
  54. package/dist/test.js.map +1 -1
  55. package/dist/types.d.ts +0 -28
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/webhook.d.ts +2 -0
  58. package/dist/webhook.d.ts.map +1 -0
  59. package/dist/webhook.js +90 -0
  60. package/dist/webhook.js.map +1 -0
  61. package/landing/DESIGN.md +343 -0
  62. package/landing/package-lock.json +1196 -7
  63. package/landing/package.json +5 -1
  64. package/landing/public/android-chrome-192x192.png +0 -0
  65. package/landing/public/android-chrome-512x512.png +0 -0
  66. package/landing/public/apple-touch-icon.png +0 -0
  67. package/landing/public/demo.gif +0 -0
  68. package/landing/public/demo.mp4 +0 -0
  69. package/landing/public/favicon-16x16.png +0 -0
  70. package/landing/public/favicon-32x32.png +0 -0
  71. package/landing/public/favicon.ico +0 -0
  72. package/landing/public/favicon.svg +3 -0
  73. package/landing/public/icon.svg +47 -0
  74. package/landing/public/logo-mono.svg +37 -0
  75. package/landing/public/logo-simple.svg +45 -0
  76. package/landing/public/logo.svg +84 -0
  77. package/landing/public/og-template.html +184 -0
  78. package/landing/public/site.webmanifest +31 -0
  79. package/landing/scripts/generate-assets.js +284 -0
  80. package/landing/scripts/generate-pngs.js +48 -0
  81. package/landing/scripts/generate-stats.js +42 -0
  82. package/landing/src/app/admin/page.tsx +348 -0
  83. package/landing/src/app/api/auth/magic-link/route.ts +73 -0
  84. package/landing/src/app/api/auth/session/route.ts +38 -0
  85. package/landing/src/app/api/auth/verify/route.ts +43 -0
  86. package/landing/src/app/api/og/route.tsx +84 -0
  87. package/landing/src/app/globals.css +439 -100
  88. package/landing/src/app/layout.tsx +37 -7
  89. package/landing/src/app/page.tsx +627 -552
  90. package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
  91. package/landing/src/app/providers/dashboard/page.tsx +589 -0
  92. package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
  93. package/landing/src/app/providers/layout.tsx +14 -0
  94. package/landing/src/app/providers/page.tsx +402 -0
  95. package/landing/src/app/providers/register/page.tsx +670 -0
  96. package/landing/src/components/ProviderDashboard.tsx +794 -0
  97. package/landing/src/hooks/useDashboardData.ts +99 -0
  98. package/landing/src/lib/apis.json +116054 -0
  99. package/landing/src/lib/convex-client.ts +106 -0
  100. package/landing/src/lib/mock-data.ts +285 -0
  101. package/landing/src/lib/stats.json +6 -0
  102. package/landing/tailwind.config.ts +12 -11
  103. package/landing/tsconfig.tsbuildinfo +1 -0
  104. package/package.json +21 -20
  105. package/scripts/SYMBOT-FIX.md +238 -0
  106. package/scripts/demo-simulation.py +177 -0
  107. package/scripts/expand-more.py +502 -0
  108. package/scripts/expand-registry.py +434 -0
  109. package/scripts/history-sanitizer.ts +272 -0
  110. package/scripts/mass-scrape.py +1308 -0
  111. package/scripts/sync-and-deploy.sh +36 -0
  112. package/src/credentials.ts +177 -0
  113. package/src/credits.ts +190 -122
  114. package/src/discovery.ts +45 -58
  115. package/src/execute.ts +350 -0
  116. package/src/index.ts +184 -32
  117. package/src/proxy.ts +24 -0
  118. package/src/registry/apis.json +95362 -202
  119. package/src/registry/apis_expanded.json +100853 -0
  120. package/src/stripe.ts +243 -0
  121. package/src/telemetry.ts +71 -0
  122. package/src/test.ts +127 -89
  123. package/src/types.ts +0 -34
  124. package/src/webhook.ts +107 -0
  125. package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
  126. package/BRIEFING.md +0 -30
  127. package/backend/convex/apiKeys.ts +0 -75
  128. package/backend/convex/purchases.ts +0 -74
  129. package/backend/convex/schema.ts +0 -45
  130. package/backend/convex/transactions.ts +0 -57
  131. package/backend/convex/users.ts +0 -94
  132. package/backend/package-lock.json +0 -521
  133. package/backend/package.json +0 -15
  134. package/dist/registry/parse_apis.py +0 -146
  135. package/dist/revenuecat.d.ts +0 -61
  136. package/dist/revenuecat.d.ts.map +0 -1
  137. package/dist/revenuecat.js +0 -166
  138. package/dist/revenuecat.js.map +0 -1
  139. package/dist/webhooks/revenuecat.d.ts +0 -48
  140. package/dist/webhooks/revenuecat.d.ts.map +0 -1
  141. package/dist/webhooks/revenuecat.js +0 -119
  142. package/dist/webhooks/revenuecat.js.map +0 -1
  143. package/docs/revenuecat-setup.md +0 -89
  144. package/landing/src/app/api/keys/route.ts +0 -71
  145. package/landing/src/app/api/log/route.ts +0 -37
  146. package/landing/src/app/api/stats/route.ts +0 -37
  147. package/landing/src/app/page.tsx.bak +0 -567
  148. package/landing/src/components/AddKeyModal.tsx +0 -159
  149. package/newsletter-template.html +0 -71
  150. package/outreach/OUTREACH-SYSTEM.md +0 -211
  151. package/outreach/email-template.html +0 -179
  152. package/outreach/targets.md +0 -133
  153. package/src/registry/parse_apis.py +0 -146
  154. package/src/revenuecat.ts +0 -239
  155. package/src/webhooks/revenuecat.ts +0 -187
  156. /package/{backend/convex → convex}/README.md +0 -0
  157. /package/{backend/convex → convex}/_generated/api.js +0 -0
  158. /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
  159. /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
  160. /package/{backend/convex → convex}/_generated/server.js +0 -0
  161. /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
+ });