@nordsym/apiclaw 1.5.7 → 1.5.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.
Files changed (142) hide show
  1. package/HTTP-API.md +306 -0
  2. package/convex/_generated/api.d.ts +2 -0
  3. package/convex/adminActivate.d.ts +3 -0
  4. package/convex/adminActivate.d.ts.map +1 -0
  5. package/convex/adminActivate.js +47 -0
  6. package/convex/adminActivate.js.map +1 -0
  7. package/convex/adminStats.d.ts +3 -0
  8. package/convex/adminStats.d.ts.map +1 -0
  9. package/convex/adminStats.js +42 -0
  10. package/convex/adminStats.js.map +1 -0
  11. package/convex/adminStats.ts +44 -0
  12. package/convex/agents.d.ts +54 -0
  13. package/convex/agents.d.ts.map +1 -0
  14. package/convex/agents.js +499 -0
  15. package/convex/agents.js.map +1 -0
  16. package/convex/analytics.d.ts +5 -0
  17. package/convex/analytics.d.ts.map +1 -0
  18. package/convex/analytics.js +166 -0
  19. package/convex/analytics.js.map +1 -0
  20. package/convex/billing.d.ts +88 -0
  21. package/convex/billing.d.ts.map +1 -0
  22. package/convex/billing.js +655 -0
  23. package/convex/billing.js.map +1 -0
  24. package/convex/capabilities.d.ts +9 -0
  25. package/convex/capabilities.d.ts.map +1 -0
  26. package/convex/capabilities.js +145 -0
  27. package/convex/capabilities.js.map +1 -0
  28. package/convex/chains.d.ts +67 -0
  29. package/convex/chains.d.ts.map +1 -0
  30. package/convex/chains.js +1042 -0
  31. package/convex/chains.js.map +1 -0
  32. package/convex/credits.d.ts +25 -0
  33. package/convex/credits.d.ts.map +1 -0
  34. package/convex/credits.js +186 -0
  35. package/convex/credits.js.map +1 -0
  36. package/convex/crons.d.ts +3 -0
  37. package/convex/crons.d.ts.map +1 -0
  38. package/convex/crons.js +17 -0
  39. package/convex/crons.js.map +1 -0
  40. package/convex/directCall.d.ts +72 -0
  41. package/convex/directCall.d.ts.map +1 -0
  42. package/convex/directCall.js +627 -0
  43. package/convex/directCall.js.map +1 -0
  44. package/convex/earnProgress.d.ts +58 -0
  45. package/convex/earnProgress.d.ts.map +1 -0
  46. package/convex/earnProgress.js +649 -0
  47. package/convex/earnProgress.js.map +1 -0
  48. package/convex/email.d.ts +14 -0
  49. package/convex/email.d.ts.map +1 -0
  50. package/convex/email.js +300 -0
  51. package/convex/email.js.map +1 -0
  52. package/convex/feedback.d.ts +7 -0
  53. package/convex/feedback.d.ts.map +1 -0
  54. package/convex/feedback.js +227 -0
  55. package/convex/feedback.js.map +1 -0
  56. package/convex/http.d.ts +3 -0
  57. package/convex/http.d.ts.map +1 -0
  58. package/convex/http.js +902 -0
  59. package/convex/http.js.map +1 -0
  60. package/convex/logs.d.ts +38 -0
  61. package/convex/logs.d.ts.map +1 -0
  62. package/convex/logs.js +487 -0
  63. package/convex/logs.js.map +1 -0
  64. package/convex/mou.d.ts +6 -0
  65. package/convex/mou.d.ts.map +1 -0
  66. package/convex/mou.js +82 -0
  67. package/convex/mou.js.map +1 -0
  68. package/convex/providerKeys.d.ts +31 -0
  69. package/convex/providerKeys.d.ts.map +1 -0
  70. package/convex/providerKeys.js +257 -0
  71. package/convex/providerKeys.js.map +1 -0
  72. package/convex/providers.d.ts +29 -0
  73. package/convex/providers.d.ts.map +1 -0
  74. package/convex/providers.js +756 -0
  75. package/convex/providers.js.map +1 -0
  76. package/convex/purchases.d.ts +7 -0
  77. package/convex/purchases.d.ts.map +1 -0
  78. package/convex/purchases.js +157 -0
  79. package/convex/purchases.js.map +1 -0
  80. package/convex/ratelimit.d.ts +4 -0
  81. package/convex/ratelimit.d.ts.map +1 -0
  82. package/convex/ratelimit.js +91 -0
  83. package/convex/ratelimit.js.map +1 -0
  84. package/convex/searchLogs.d.ts +4 -0
  85. package/convex/searchLogs.d.ts.map +1 -0
  86. package/convex/searchLogs.js +129 -0
  87. package/convex/searchLogs.js.map +1 -0
  88. package/convex/spendAlerts.d.ts +36 -0
  89. package/convex/spendAlerts.d.ts.map +1 -0
  90. package/convex/spendAlerts.js +380 -0
  91. package/convex/spendAlerts.js.map +1 -0
  92. package/convex/stripeActions.d.ts +19 -0
  93. package/convex/stripeActions.d.ts.map +1 -0
  94. package/convex/stripeActions.js +411 -0
  95. package/convex/stripeActions.js.map +1 -0
  96. package/convex/teams.d.ts +21 -0
  97. package/convex/teams.d.ts.map +1 -0
  98. package/convex/teams.js +215 -0
  99. package/convex/teams.js.map +1 -0
  100. package/convex/telemetry.d.ts +4 -0
  101. package/convex/telemetry.d.ts.map +1 -0
  102. package/convex/telemetry.js +74 -0
  103. package/convex/telemetry.js.map +1 -0
  104. package/convex/usage.d.ts +27 -0
  105. package/convex/usage.d.ts.map +1 -0
  106. package/convex/usage.js +229 -0
  107. package/convex/usage.js.map +1 -0
  108. package/convex/waitlist.d.ts +4 -0
  109. package/convex/waitlist.d.ts.map +1 -0
  110. package/convex/waitlist.js +49 -0
  111. package/convex/waitlist.js.map +1 -0
  112. package/convex/webhooks.d.ts +12 -0
  113. package/convex/webhooks.d.ts.map +1 -0
  114. package/convex/webhooks.js +410 -0
  115. package/convex/webhooks.js.map +1 -0
  116. package/convex/workspaces.d.ts +29 -0
  117. package/convex/workspaces.d.ts.map +1 -0
  118. package/convex/workspaces.js +880 -0
  119. package/convex/workspaces.js.map +1 -0
  120. package/dist/analytics.d.ts +1 -1
  121. package/dist/analytics.d.ts.map +1 -1
  122. package/dist/analytics.js +51 -2
  123. package/dist/analytics.js.map +1 -1
  124. package/dist/bin-http.d.ts +7 -0
  125. package/dist/bin-http.d.ts.map +1 -0
  126. package/dist/bin-http.js +43 -0
  127. package/dist/bin-http.js.map +1 -0
  128. package/dist/http-api.d.ts +16 -0
  129. package/dist/http-api.d.ts.map +1 -0
  130. package/dist/http-api.js +269 -0
  131. package/dist/http-api.js.map +1 -0
  132. package/dist/index.js +27 -2
  133. package/dist/index.js.map +1 -1
  134. package/landing/scripts/generate-stats.js +9 -5
  135. package/landing/src/app/layout.tsx +5 -5
  136. package/landing/src/app/page.tsx +2 -0
  137. package/landing/src/lib/stats.json +2 -2
  138. package/package.json +3 -2
  139. package/src/analytics.ts +53 -2
  140. package/src/bin-http.ts +45 -0
  141. package/src/http-api.ts +302 -0
  142. package/src/index.ts +28 -2
@@ -0,0 +1,302 @@
1
+ /**
2
+ * APIClaw HTTP API Server
3
+ * Provides REST endpoints for headless agents (Hivr bees, webhooks, etc)
4
+ *
5
+ * Endpoints:
6
+ * - GET /api/discover?query=...&agentId=...
7
+ * - POST /api/call_api { provider, action, params, agentId }
8
+ * - GET /health
9
+ *
10
+ * Auth: Whitelist-based for Hivr bees
11
+ */
12
+
13
+ import { createServer, IncomingMessage, ServerResponse } from 'http';
14
+ import { URL } from 'url';
15
+ import { discoverAPIs } from './discovery.js';
16
+ import { isOpenAPI, executeOpenAPI } from './open-apis.js';
17
+ import { executeMetered } from './metered.js';
18
+ import { logAPICall } from './analytics.js';
19
+ import { getMachineFingerprint } from './session.js';
20
+
21
+ // Hivr bees whitelist - these agents get free unlimited access
22
+ const HIVR_BEES_WHITELIST = [
23
+ 'bytebee',
24
+ 'analyzerbee',
25
+ 'buildbee',
26
+ 'buzzwriter',
27
+ 'hivemind',
28
+ 'hivesage',
29
+ 'symbot',
30
+ 'hivrqueen',
31
+ 'marketmaven',
32
+ 'reconbee',
33
+ 'sprintbee',
34
+ 'quillbee',
35
+ // Add more as Hivr grows
36
+ ];
37
+
38
+ interface APIRequest {
39
+ provider: string;
40
+ action: string;
41
+ params: Record<string, any>;
42
+ agentId: string;
43
+ }
44
+
45
+ /**
46
+ * Check if agent is authorized (Hivr bee)
47
+ */
48
+ function isAuthorized(agentId: string | undefined): boolean {
49
+ if (!agentId) return false;
50
+ const normalized = agentId.toLowerCase().trim();
51
+ return HIVR_BEES_WHITELIST.includes(normalized);
52
+ }
53
+
54
+ /**
55
+ * Parse JSON body from request
56
+ */
57
+ async function parseBody<T>(req: IncomingMessage): Promise<T> {
58
+ return new Promise((resolve, reject) => {
59
+ let body = '';
60
+ req.on('data', chunk => body += chunk.toString());
61
+ req.on('end', () => {
62
+ try {
63
+ resolve(JSON.parse(body));
64
+ } catch (e) {
65
+ reject(new Error('Invalid JSON'));
66
+ }
67
+ });
68
+ req.on('error', reject);
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Send JSON response
74
+ */
75
+ function sendJSON(res: ServerResponse, status: number, data: any): void {
76
+ res.writeHead(status, {
77
+ 'Content-Type': 'application/json',
78
+ 'Access-Control-Allow-Origin': '*',
79
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
80
+ 'Access-Control-Allow-Headers': 'Content-Type, X-Agent-Id',
81
+ });
82
+ res.end(JSON.stringify(data));
83
+ }
84
+
85
+ /**
86
+ * Handle /api/discover
87
+ * GET /api/discover?query=web+search&agentId=bytebee&category=Search&maxResults=5
88
+ */
89
+ async function handleDiscover(req: IncomingMessage, res: ServerResponse, url: URL): Promise<void> {
90
+ const query = url.searchParams.get('query');
91
+ const agentId = url.searchParams.get('agentId');
92
+ const category = url.searchParams.get('category') || undefined;
93
+ const maxResults = parseInt(url.searchParams.get('maxResults') || '5');
94
+
95
+ if (!query) {
96
+ sendJSON(res, 400, { error: 'Missing query parameter' });
97
+ return;
98
+ }
99
+
100
+ if (!isAuthorized(agentId || undefined)) {
101
+ sendJSON(res, 403, {
102
+ error: 'Unauthorized',
103
+ message: 'This endpoint is restricted to Hivr bees. Contact admin@nordsym.com for access.',
104
+ });
105
+ return;
106
+ }
107
+
108
+ const startTime = Date.now();
109
+ const results = discoverAPIs(query, { category, maxResults });
110
+ const responseTimeMs = Date.now() - startTime;
111
+
112
+ // Log to analytics
113
+ logAPICall({
114
+ timestamp: new Date().toISOString(),
115
+ provider: 'apiclaw_discovery',
116
+ action: 'discover',
117
+ type: 'open',
118
+ userId: `hivr:${agentId}`,
119
+ success: true,
120
+ latencyMs: responseTimeMs,
121
+ });
122
+
123
+ sendJSON(res, 200, {
124
+ success: true,
125
+ query,
126
+ results: results.map(r => ({
127
+ provider: r.provider,
128
+ score: r.relevance_score,
129
+ reasons: r.match_reasons,
130
+ })),
131
+ count: results.length,
132
+ responseTimeMs,
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Handle /api/call_api
138
+ * POST /api/call_api
139
+ * Body: { provider: "brave_search", action: "search", params: { query: "AI news" }, agentId: "bytebee" }
140
+ */
141
+ async function handleCallAPI(req: IncomingMessage, res: ServerResponse): Promise<void> {
142
+ let body: APIRequest;
143
+
144
+ try {
145
+ body = await parseBody<APIRequest>(req);
146
+ } catch (e) {
147
+ sendJSON(res, 400, { error: 'Invalid JSON body' });
148
+ return;
149
+ }
150
+
151
+ const { provider, action, params, agentId } = body;
152
+
153
+ if (!provider || !action || !params || !agentId) {
154
+ sendJSON(res, 400, {
155
+ error: 'Missing required fields',
156
+ required: ['provider', 'action', 'params', 'agentId']
157
+ });
158
+ return;
159
+ }
160
+
161
+ if (!isAuthorized(agentId)) {
162
+ sendJSON(res, 403, {
163
+ error: 'Unauthorized',
164
+ message: 'This endpoint is restricted to Hivr bees. Contact admin@nordsym.com for access.',
165
+ });
166
+ return;
167
+ }
168
+
169
+ const startTime = Date.now();
170
+ let result: any;
171
+ let apiType: 'open' | 'direct';
172
+ let success = true;
173
+ let error: string | undefined;
174
+
175
+ try {
176
+ if (isOpenAPI(provider)) {
177
+ apiType = 'open';
178
+ result = await executeOpenAPI(provider, action, params);
179
+ success = result.success;
180
+ error = result.error;
181
+ } else {
182
+ apiType = 'direct';
183
+ // For Direct Call APIs, use Hivr's workspace/credentials
184
+ // TODO: Get Hivr workspace token from env or config
185
+ const customerKey = process.env.APICLAW_HIVR_CUSTOMER_KEY;
186
+ const stripeCustomerId = process.env.APICLAW_HIVR_STRIPE_CUSTOMER;
187
+
188
+ result = await executeMetered(provider, action, params, {
189
+ customerId: stripeCustomerId,
190
+ customerKey,
191
+ userId: `hivr:${agentId}`,
192
+ });
193
+ success = result.success;
194
+ error = result.error;
195
+ }
196
+ } catch (e: any) {
197
+ success = false;
198
+ error = e.message;
199
+ result = { success: false, error: error };
200
+ }
201
+
202
+ const latencyMs = Date.now() - startTime;
203
+
204
+ // Log to analytics
205
+ logAPICall({
206
+ timestamp: new Date().toISOString(),
207
+ provider,
208
+ action,
209
+ type: apiType!,
210
+ userId: `hivr:${agentId}`,
211
+ success,
212
+ latencyMs,
213
+ error,
214
+ });
215
+
216
+ sendJSON(res, success ? 200 : 500, {
217
+ success,
218
+ provider,
219
+ action,
220
+ agentId,
221
+ data: result.data,
222
+ error: result.error,
223
+ latencyMs,
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Handle OPTIONS (CORS preflight)
229
+ */
230
+ function handleOptions(res: ServerResponse): void {
231
+ res.writeHead(204, {
232
+ 'Access-Control-Allow-Origin': '*',
233
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
234
+ 'Access-Control-Allow-Headers': 'Content-Type, X-Agent-Id',
235
+ 'Access-Control-Max-Age': '86400',
236
+ });
237
+ res.end();
238
+ }
239
+
240
+ /**
241
+ * Main request handler
242
+ */
243
+ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
244
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
245
+
246
+ console.log(`[APIClaw HTTP] ${req.method} ${url.pathname}`);
247
+
248
+ // CORS preflight
249
+ if (req.method === 'OPTIONS') {
250
+ handleOptions(res);
251
+ return;
252
+ }
253
+
254
+ // Health check
255
+ if (url.pathname === '/health') {
256
+ sendJSON(res, 200, { status: 'ok', service: 'apiclaw-http-api' });
257
+ return;
258
+ }
259
+
260
+ // Route requests
261
+ if (url.pathname === '/api/discover' && req.method === 'GET') {
262
+ await handleDiscover(req, res, url);
263
+ return;
264
+ }
265
+
266
+ if (url.pathname === '/api/call_api' && req.method === 'POST') {
267
+ await handleCallAPI(req, res);
268
+ return;
269
+ }
270
+
271
+ // 404
272
+ sendJSON(res, 404, { error: 'Not found' });
273
+ }
274
+
275
+ /**
276
+ * Start HTTP server
277
+ */
278
+ export function startHTTPServer(port: number = 3000): void {
279
+ const server = createServer(async (req, res) => {
280
+ try {
281
+ await handleRequest(req, res);
282
+ } catch (error: any) {
283
+ console.error('[APIClaw HTTP] Error:', error);
284
+ sendJSON(res, 500, { error: 'Internal server error', message: error.message });
285
+ }
286
+ });
287
+
288
+ server.listen(port, () => {
289
+ console.log(`\nšŸ¦ž APIClaw HTTP API running on http://localhost:${port}`);
290
+ console.log(` GET /api/discover?query=...&agentId=...`);
291
+ console.log(` POST /api/call_api`);
292
+ console.log(` GET /health\n`);
293
+ });
294
+
295
+ server.on('error', (error: any) => {
296
+ if (error.code === 'EADDRINUSE') {
297
+ console.error(`[APIClaw HTTP] Port ${port} is already in use`);
298
+ } else {
299
+ console.error('[APIClaw HTTP] Server error:', error);
300
+ }
301
+ });
302
+ }
package/src/index.ts CHANGED
@@ -865,7 +865,33 @@ Docs: https://apiclaw.nordsym.com
865
865
  const responseTimeMs = Date.now() - startTime;
866
866
  trackSearch(query, results.length, responseTimeMs);
867
867
 
868
- // Log search to Convex for analytics
868
+ // Log search to Convex analytics (authenticated + anonymous)
869
+ const analyticsUserId = workspaceContext?.workspaceId || `anon:${getMachineFingerprint()}`;
870
+ const convexUrl = process.env.APICLAW_CONVEX_URL || process.env.NEXT_PUBLIC_CONVEX_URL;
871
+ if (convexUrl) {
872
+ fetch(`${convexUrl}/api/mutation`, {
873
+ method: 'POST',
874
+ headers: { 'Content-Type': 'application/json' },
875
+ body: JSON.stringify({
876
+ path: 'analytics:log',
877
+ args: {
878
+ event: 'search_query',
879
+ provider: undefined,
880
+ query,
881
+ identifier: analyticsUserId,
882
+ metadata: {
883
+ resultCount: results.length,
884
+ matchedProviders: results.slice(0, 10).map(r => r.provider.id),
885
+ responseTimeMs,
886
+ category,
887
+ authenticated: !!workspaceContext,
888
+ },
889
+ },
890
+ }),
891
+ }).catch(() => {}); // Fire and forget
892
+ }
893
+
894
+ // Log search to searchLogs table (authenticated only - requires workspace)
869
895
  if (workspaceContext?.sessionToken) {
870
896
  const searchLogPayload = {
871
897
  path: 'searchLogs:log',
@@ -879,7 +905,7 @@ Docs: https://apiclaw.nordsym.com
879
905
  },
880
906
  };
881
907
 
882
- fetch('https://brilliant-puffin-712.eu-west-1.convex.cloud/api/mutation', {
908
+ fetch(`${convexUrl}/api/mutation`, {
883
909
  method: 'POST',
884
910
  headers: { 'Content-Type': 'application/json' },
885
911
  body: JSON.stringify(searchLogPayload),