@nordsym/apiclaw 2.2.0 → 2.2.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 (176) hide show
  1. package/README.md +15 -2
  2. package/dist/bin-http.js +0 -0
  3. package/dist/bin.bundled.js +79288 -0
  4. package/dist/gateway-client.d.ts.map +1 -1
  5. package/dist/gateway-client.js +24 -2
  6. package/dist/gateway-client.js.map +1 -1
  7. package/dist/index.bundled.js +61263 -0
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -2
  11. package/.claude/settings.local.json +0 -13
  12. package/.env.prod +0 -1
  13. package/apiclaw-README.md +0 -494
  14. package/convex/_generated/api.d.ts +0 -145
  15. package/convex/_generated/api.js +0 -23
  16. package/convex/_generated/dataModel.d.ts +0 -60
  17. package/convex/_generated/server.d.ts +0 -143
  18. package/convex/_generated/server.js +0 -93
  19. package/convex/_listWorkspaces.ts +0 -13
  20. package/convex/adminActivate.ts +0 -53
  21. package/convex/adminStats.ts +0 -306
  22. package/convex/agents.ts +0 -939
  23. package/convex/analytics.ts +0 -187
  24. package/convex/apiKeys.ts +0 -220
  25. package/convex/backfillAnalytics.ts +0 -272
  26. package/convex/backfillSearchLogs.ts +0 -35
  27. package/convex/billing.ts +0 -834
  28. package/convex/capabilities.ts +0 -157
  29. package/convex/chains.ts +0 -1318
  30. package/convex/credits.ts +0 -211
  31. package/convex/crons.ts +0 -65
  32. package/convex/debugFilestackLogs.ts +0 -16
  33. package/convex/debugGetToken.ts +0 -18
  34. package/convex/directCall.ts +0 -713
  35. package/convex/earnProgress.ts +0 -753
  36. package/convex/email.ts +0 -329
  37. package/convex/feedback.ts +0 -265
  38. package/convex/funnel.ts +0 -431
  39. package/convex/guards.ts +0 -174
  40. package/convex/http.ts +0 -3756
  41. package/convex/inbound.ts +0 -32
  42. package/convex/logs.ts +0 -701
  43. package/convex/migrateFilestack.ts +0 -81
  44. package/convex/migratePartnersProd.ts +0 -174
  45. package/convex/migratePratham.ts +0 -126
  46. package/convex/migrateProviderWorkspaces.ts +0 -175
  47. package/convex/mou.ts +0 -91
  48. package/convex/nurture.ts +0 -355
  49. package/convex/providerKeys.ts +0 -289
  50. package/convex/providers.ts +0 -1135
  51. package/convex/purchases.ts +0 -183
  52. package/convex/ratelimit.ts +0 -104
  53. package/convex/schema.ts +0 -926
  54. package/convex/searchLogs.ts +0 -265
  55. package/convex/seedAPILayerAPIs.ts +0 -191
  56. package/convex/seedDirectCallConfigs.ts +0 -336
  57. package/convex/seedPratham.ts +0 -149
  58. package/convex/spendAlerts.ts +0 -442
  59. package/convex/stripeActions.ts +0 -607
  60. package/convex/teams.ts +0 -243
  61. package/convex/telemetry.ts +0 -81
  62. package/convex/tsconfig.json +0 -25
  63. package/convex/updateAPIStatus.ts +0 -44
  64. package/convex/usage.ts +0 -260
  65. package/convex/usageReports.ts +0 -357
  66. package/convex/waitlist.ts +0 -55
  67. package/convex/webhooks.ts +0 -494
  68. package/convex/workspaceSettings.ts +0 -143
  69. package/convex/workspaces.ts +0 -1331
  70. package/convex.json +0 -3
  71. package/direct-test.mjs +0 -51
  72. package/email-templates/filestack-provider-outreach.html +0 -162
  73. package/email-templates/partnership-template.html +0 -116
  74. package/email-templates/pratham-draft-preview.txt +0 -57
  75. package/email-templates/pratham-partnership-draft.html +0 -141
  76. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  77. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  78. package/reports/pipeline/acquire_apisguru.json +0 -17
  79. package/reports/pipeline/capabilities.json +0 -38
  80. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  81. package/reports/pipeline/discover_github.json +0 -25
  82. package/reports/pipeline/discover_github_repos.json +0 -49
  83. package/reports/pipeline/discover_swaggerhub.json +0 -24
  84. package/reports/pipeline/discover_well_known.json +0 -23
  85. package/reports/pipeline/fetch_specs.json +0 -19
  86. package/reports/pipeline/generate_providers.json +0 -14
  87. package/reports/pipeline/match_registry.json +0 -11
  88. package/reports/pipeline/parse_specs.json +0 -17
  89. package/reports/pipeline/promote_candidates.json +0 -34
  90. package/reports/pipeline/validate.json +0 -30
  91. package/reports/pipeline/validate_smoke_details.json +0 -3835
  92. package/reports/session-report-2026-04-05.html +0 -433
  93. package/seed-apis-direct.mjs +0 -106
  94. package/src/access-control.ts +0 -174
  95. package/src/adapters/base.ts +0 -364
  96. package/src/adapters/claude-desktop.ts +0 -41
  97. package/src/adapters/cline.ts +0 -88
  98. package/src/adapters/continue.ts +0 -91
  99. package/src/adapters/cursor.ts +0 -43
  100. package/src/adapters/custom.ts +0 -188
  101. package/src/adapters/detect.ts +0 -202
  102. package/src/adapters/index.ts +0 -47
  103. package/src/adapters/windsurf.ts +0 -44
  104. package/src/bin-http.ts +0 -45
  105. package/src/bin.ts +0 -34
  106. package/src/capability-router.ts +0 -331
  107. package/src/chainExecutor.ts +0 -730
  108. package/src/chainResolver.test.ts +0 -246
  109. package/src/chainResolver.ts +0 -658
  110. package/src/cli/commands/demo.ts +0 -109
  111. package/src/cli/commands/doctor.ts +0 -435
  112. package/src/cli/commands/index.ts +0 -9
  113. package/src/cli/commands/login.ts +0 -203
  114. package/src/cli/commands/mcp-install.ts +0 -373
  115. package/src/cli/commands/restore.ts +0 -333
  116. package/src/cli/commands/setup.ts +0 -297
  117. package/src/cli/commands/uninstall.ts +0 -240
  118. package/src/cli/index.ts +0 -148
  119. package/src/cli.ts +0 -370
  120. package/src/confirmation.ts +0 -296
  121. package/src/credentials.ts +0 -455
  122. package/src/credits.ts +0 -329
  123. package/src/crypto.ts +0 -75
  124. package/src/discovery.ts +0 -568
  125. package/src/enterprise/env.ts +0 -156
  126. package/src/enterprise/index.ts +0 -7
  127. package/src/enterprise/script-generator.ts +0 -481
  128. package/src/execute-dynamic.ts +0 -617
  129. package/src/execute.ts +0 -2386
  130. package/src/funnel-client.ts +0 -168
  131. package/src/funnel.test.ts +0 -187
  132. package/src/gateway-client.ts +0 -192
  133. package/src/hivr-whitelist.ts +0 -110
  134. package/src/http-api.ts +0 -286
  135. package/src/http-server-minimal.ts +0 -154
  136. package/src/index.ts +0 -2702
  137. package/src/intelligent-gateway.ts +0 -339
  138. package/src/mcp-analytics.ts +0 -156
  139. package/src/metered.ts +0 -149
  140. package/src/open-apis-generated.ts +0 -157
  141. package/src/open-apis.ts +0 -558
  142. package/src/postinstall.ts +0 -40
  143. package/src/product-whitelist.ts +0 -246
  144. package/src/proxy.ts +0 -36
  145. package/src/registration-guard.ts +0 -117
  146. package/src/session.ts +0 -129
  147. package/src/stripe.ts +0 -497
  148. package/src/telemetry.ts +0 -71
  149. package/src/test.ts +0 -135
  150. package/src/types/convex-api.d.ts +0 -20
  151. package/src/types/convex-api.ts +0 -21
  152. package/src/types.ts +0 -109
  153. package/src/ui/colors.ts +0 -219
  154. package/src/ui/errors.ts +0 -394
  155. package/src/ui/index.ts +0 -17
  156. package/src/ui/prompts.ts +0 -390
  157. package/src/ui/spinner.ts +0 -325
  158. package/src/utils/backup.ts +0 -224
  159. package/src/utils/config.ts +0 -318
  160. package/src/utils/os.ts +0 -124
  161. package/src/utils/paths.ts +0 -203
  162. package/src/webhook.ts +0 -107
  163. package/test-10-working.cjs +0 -97
  164. package/test-14-final.cjs +0 -96
  165. package/test-actual-handlers.ts +0 -92
  166. package/test-apilayer-all-14.ts +0 -249
  167. package/test-apilayer-fixed.ts +0 -248
  168. package/test-direct-endpoints.ts +0 -174
  169. package/test-exact-endpoints.ts +0 -144
  170. package/test-final.ts +0 -83
  171. package/test-full-routing.ts +0 -100
  172. package/test-handlers-correct.ts +0 -217
  173. package/test-numverify-key.ts +0 -41
  174. package/test-via-handlers.ts +0 -92
  175. package/test-worldnews.mjs +0 -26
  176. package/tsconfig.json +0 -20
@@ -1,1135 +0,0 @@
1
- import { mutation, query } from "./_generated/server";
2
- import { v } from "convex/values";
3
-
4
- // Register a new provider and their first API
5
- export const registerProvider = mutation({
6
- args: {
7
- provider: v.object({
8
- name: v.string(),
9
- email: v.string(),
10
- website: v.optional(v.string()),
11
- }),
12
- api: v.object({
13
- name: v.string(),
14
- description: v.string(),
15
- category: v.string(),
16
- openApiUrl: v.optional(v.string()),
17
- docsUrl: v.optional(v.string()),
18
- pricingModel: v.string(),
19
- pricingNotes: v.optional(v.string()),
20
- }),
21
- },
22
- handler: async (ctx, args) => {
23
- const now = Date.now();
24
-
25
- // Check if provider already exists by email
26
- const existing = await ctx.db
27
- .query("providers")
28
- .withIndex("by_email", (q) => q.eq("email", args.provider.email))
29
- .first();
30
-
31
- let providerId;
32
-
33
- if (existing) {
34
- // Use existing provider
35
- providerId = existing._id;
36
- } else {
37
- // Create new provider - auto-approve for now
38
- providerId = await ctx.db.insert("providers", {
39
- name: args.provider.name,
40
- email: args.provider.email,
41
- website: args.provider.website,
42
- status: "approved", // Auto-approve for MVP
43
- createdAt: now,
44
- updatedAt: now,
45
- approvedAt: now,
46
- });
47
- }
48
-
49
- // Create the API listing - auto-approve for now
50
- const apiId = await ctx.db.insert("providerAPIs", {
51
- providerId,
52
- name: args.api.name,
53
- description: args.api.description,
54
- category: args.api.category,
55
- openApiUrl: args.api.openApiUrl,
56
- docsUrl: args.api.docsUrl,
57
- pricingModel: args.api.pricingModel,
58
- pricingNotes: args.api.pricingNotes,
59
- status: "approved", // Auto-approve for MVP
60
- createdAt: now,
61
- approvedAt: now,
62
- discoveryCount: 0,
63
- });
64
-
65
- // Find or create workspace for this provider email
66
- const emailLower = args.provider.email.toLowerCase();
67
- let workspace = await ctx.db
68
- .query("workspaces")
69
- .withIndex("by_email", (q) => q.eq("email", emailLower))
70
- .first();
71
-
72
- if (!workspace) {
73
- const wsId = await ctx.db.insert("workspaces", {
74
- email: emailLower,
75
- status: "active",
76
- tier: "free",
77
- usageCount: 0,
78
- usageLimit: 50,
79
- weeklyUsageCount: 0,
80
- weeklyUsageLimit: 50,
81
- hourlyUsageCount: 0,
82
- createdAt: now,
83
- updatedAt: now,
84
- });
85
- workspace = await ctx.db.get(wsId);
86
- }
87
-
88
- // Link provider → workspace (if not already)
89
- const provider = await ctx.db.get(providerId);
90
- if (provider && !(provider as any).workspaceId) {
91
- await ctx.db.patch(providerId, { workspaceId: workspace!._id } as any);
92
- }
93
-
94
- // Create unified session (agentSessions only - legacy sessions table deprecated)
95
- const sessionToken = generateToken();
96
- await ctx.db.insert("agentSessions", {
97
- workspaceId: workspace!._id,
98
- sessionToken,
99
- lastUsedAt: now,
100
- createdAt: now,
101
- });
102
-
103
- return { providerId, apiId, sessionToken };
104
- },
105
- });
106
-
107
- // Get provider by email
108
- export const getProviderByEmail = query({
109
- args: { email: v.string() },
110
- handler: async (ctx, args) => {
111
- return await ctx.db
112
- .query("providers")
113
- .withIndex("by_email", (q) => q.eq("email", args.email))
114
- .first();
115
- },
116
- });
117
-
118
- // Get all APIs for a provider
119
- export const getProviderAPIs = query({
120
- args: { providerId: v.id("providers") },
121
- handler: async (ctx, args) => {
122
- return await ctx.db
123
- .query("providerAPIs")
124
- .withIndex("by_providerId", (q) => q.eq("providerId", args.providerId))
125
- .collect();
126
- },
127
- });
128
-
129
- // Get all approved APIs (for the registry)
130
- export const getApprovedAPIs = query({
131
- args: {
132
- category: v.optional(v.string()),
133
- limit: v.optional(v.number()),
134
- },
135
- handler: async (ctx, args) => {
136
- const query = ctx.db
137
- .query("providerAPIs")
138
- .withIndex("by_status", (q) => q.eq("status", "approved"));
139
-
140
- const apis = await query.collect();
141
-
142
- // Filter by category if provided
143
- let filtered = args.category
144
- ? apis.filter((api) => api.category === args.category)
145
- : apis;
146
-
147
- // Apply limit
148
- if (args.limit) {
149
- filtered = filtered.slice(0, args.limit);
150
- }
151
-
152
- return filtered;
153
- },
154
- });
155
-
156
- // Get API categories with counts
157
- export const getCategories = query({
158
- handler: async (ctx) => {
159
- const apis = await ctx.db
160
- .query("providerAPIs")
161
- .withIndex("by_status", (q) => q.eq("status", "approved"))
162
- .collect();
163
-
164
- const categories: Record<string, number> = {};
165
- for (const api of apis) {
166
- categories[api.category] = (categories[api.category] || 0) + 1;
167
- }
168
-
169
- return Object.entries(categories)
170
- .map(([name, count]) => ({ name, count }))
171
- .sort((a, b) => b.count - a.count);
172
- },
173
- });
174
-
175
- // Increment discovery count when an agent finds an API
176
- export const trackDiscovery = mutation({
177
- args: { apiId: v.id("providerAPIs") },
178
- handler: async (ctx, args) => {
179
- const api = await ctx.db.get(args.apiId);
180
- if (!api) return;
181
-
182
- await ctx.db.patch(args.apiId, {
183
- discoveryCount: (api.discoveryCount || 0) + 1,
184
- lastDiscoveredAt: Date.now(),
185
- });
186
- },
187
- });
188
-
189
- // Unified discovery logging
190
- // Single source of truth: apiLogs. discoveryCount on My APIs is derived from apiLogs.
191
- export const logDiscovery = mutation({
192
- args: {
193
- provider: v.string(),
194
- query: v.string(),
195
- latencyMs: v.number(),
196
- callerWorkspaceId: v.string(),
197
- },
198
- handler: async (ctx, args) => {
199
- // Resolve provider → workspace dynamically (no hardcoded email maps)
200
- const providerNameLower = args.provider.toLowerCase();
201
- const allProviders = await ctx.db.query("providers").collect();
202
- const providerRecord = allProviders.find(
203
- (p) => p.name.toLowerCase() === providerNameLower
204
- );
205
- if (!providerRecord || !(providerRecord as any).workspaceId) return { logged: false };
206
-
207
- const workspace = await ctx.db.get((providerRecord as any).workspaceId);
208
- if (!workspace) return { logged: false };
209
- const wsId = workspace._id as any;
210
-
211
- // 1. Log to apiLogs (source of truth for Analytics)
212
- await ctx.db.insert("apiLogs", {
213
- workspaceId: wsId,
214
- sessionToken: "",
215
- provider: args.provider,
216
- action: `discovery:${args.query}`,
217
- status: "success",
218
- latencyMs: args.latencyMs,
219
- direction: "inbound",
220
- callerWorkspaceId: args.callerWorkspaceId,
221
- createdAt: Date.now(),
222
- });
223
-
224
- // 2. Increment discoveryCount on MATCHING APIs only
225
- const apis = await ctx.db
226
- .query("providerAPIs")
227
- .withIndex("by_providerId", (q) => q.eq("providerId", providerRecord._id))
228
- .collect();
229
- const queryLower = args.query.toLowerCase();
230
- const queryWords = queryLower.split(/\s+/).filter((w: string) => w.length > 2);
231
-
232
- let matched = 0;
233
- for (const api of apis) {
234
- const apiText = `${api.name} ${api.description || ""}`.toLowerCase();
235
- if (queryWords.some((w: string) => apiText.includes(w))) {
236
- await ctx.db.patch(api._id, {
237
- discoveryCount: ((api as any).discoveryCount || 0) + 1,
238
- lastDiscoveredAt: Date.now(),
239
- });
240
- matched++;
241
- }
242
- }
243
- return { logged: true, matched };
244
- },
245
- });
246
-
247
- // Legacy: Track discovery by provider name (kept for backwards compat)
248
- export const trackDiscoveryByProvider = mutation({
249
- args: { provider: v.string(), query: v.string() },
250
- handler: async (ctx, args) => {
251
- // Resolve provider dynamically by name (no hardcoded email maps)
252
- const providerNameLower = args.provider.toLowerCase();
253
- const allProviders = await ctx.db.query("providers").collect();
254
- const provider = allProviders.find(
255
- (p) => p.name.toLowerCase() === providerNameLower
256
- );
257
- if (!provider) return { updated: 0, error: "provider not found" };
258
-
259
- // Get all APIs for this provider
260
- const providerApis = await ctx.db
261
- .query("providerAPIs")
262
- .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
263
- .collect();
264
-
265
- // Increment discoveryCount on ALL provider APIs
266
- for (const api of providerApis) {
267
- await ctx.db.patch(api._id, {
268
- discoveryCount: ((api as any).discoveryCount || 0) + 1,
269
- lastDiscoveredAt: Date.now(),
270
- });
271
- }
272
-
273
- return { updated: providerApis.length };
274
- },
275
- });
276
-
277
- // Admin: List pending providers
278
- export const getPendingProviders = query({
279
- handler: async (ctx) => {
280
- return await ctx.db
281
- .query("providers")
282
- .withIndex("by_status", (q) => q.eq("status", "pending"))
283
- .collect();
284
- },
285
- });
286
-
287
- // Admin: Approve provider
288
- export const approveProvider = mutation({
289
- args: { providerId: v.id("providers") },
290
- handler: async (ctx, args) => {
291
- await ctx.db.patch(args.providerId, {
292
- status: "approved",
293
- approvedAt: Date.now(),
294
- updatedAt: Date.now(),
295
- });
296
- },
297
- });
298
-
299
- // Admin: Reject provider
300
- export const rejectProvider = mutation({
301
- args: { providerId: v.id("providers") },
302
- handler: async (ctx, args) => {
303
- await ctx.db.patch(args.providerId, {
304
- status: "rejected",
305
- updatedAt: Date.now(),
306
- });
307
- },
308
- });
309
-
310
- // Get provider stats
311
- export const getProviderStats = query({
312
- handler: async (ctx) => {
313
- const providers = await ctx.db.query("providers").collect();
314
- const apis = await ctx.db.query("providerAPIs").collect();
315
-
316
- return {
317
- totalProviders: providers.length,
318
- approvedProviders: providers.filter((p) => p.status === "approved").length,
319
- pendingProviders: providers.filter((p) => p.status === "pending").length,
320
- totalAPIs: apis.length,
321
- approvedAPIs: apis.filter((a) => a.status === "approved").length,
322
- pendingAPIs: apis.filter((a) => a.status === "pending").length,
323
- totalDiscoveries: apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0),
324
- };
325
- },
326
- });
327
-
328
- // ============================================
329
- // DASHBOARD AUTH & SESSION FUNCTIONS
330
- // ============================================
331
-
332
- // Create magic link for email auth (unified: writes to workspaceMagicLinks)
333
- export const createMagicLink = mutation({
334
- args: { email: v.string() },
335
- handler: async (ctx, { email }) => {
336
- const token = generateToken();
337
- const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes
338
-
339
- await ctx.db.insert("workspaceMagicLinks", {
340
- email: email.toLowerCase(),
341
- token,
342
- expiresAt,
343
- createdAt: Date.now(),
344
- });
345
-
346
- return { token, expiresAt };
347
- },
348
- });
349
-
350
- // Verify magic link and create unified session (workspace + provider)
351
- export const verifyMagicLink = mutation({
352
- args: { token: v.string() },
353
- handler: async (ctx, { token }) => {
354
- const magicLink = await ctx.db
355
- .query("workspaceMagicLinks")
356
- .withIndex("by_token", (q) => q.eq("token", token))
357
- .first();
358
-
359
- if (!magicLink) {
360
- return { success: false, error: "Invalid token" };
361
- }
362
-
363
- if (magicLink.expiresAt < Date.now()) {
364
- return { success: false, error: "Token expired" };
365
- }
366
-
367
- if (magicLink.usedAt) {
368
- return { success: false, error: "Token already used" };
369
- }
370
-
371
- // Mark as used
372
- await ctx.db.patch(magicLink._id, { usedAt: Date.now() });
373
-
374
- const now = Date.now();
375
-
376
- // Find or create workspace
377
- let workspace = await ctx.db
378
- .query("workspaces")
379
- .withIndex("by_email", (q) => q.eq("email", magicLink.email))
380
- .first();
381
-
382
- if (!workspace) {
383
- const wsId = await ctx.db.insert("workspaces", {
384
- email: magicLink.email,
385
- status: "active",
386
- tier: "free",
387
- usageCount: 0,
388
- usageLimit: 50,
389
- weeklyUsageCount: 0,
390
- weeklyUsageLimit: 50,
391
- hourlyUsageCount: 0,
392
- createdAt: now,
393
- updatedAt: now,
394
- });
395
- workspace = await ctx.db.get(wsId);
396
- }
397
-
398
- // Find or create provider
399
- let provider = await ctx.db
400
- .query("providers")
401
- .withIndex("by_email", (q) => q.eq("email", magicLink.email))
402
- .first();
403
-
404
- if (!provider) {
405
- const providerId = await ctx.db.insert("providers", {
406
- email: magicLink.email,
407
- name: magicLink.email.split("@")[0],
408
- status: "approved",
409
- workspaceId: workspace!._id,
410
- createdAt: now,
411
- updatedAt: now,
412
- } as any);
413
- provider = await ctx.db.get(providerId);
414
- } else if (!(provider as any).workspaceId) {
415
- // Link existing provider to workspace
416
- await ctx.db.patch(provider._id, { workspaceId: workspace!._id } as any);
417
- }
418
-
419
- // Create unified session (agentSessions)
420
- const sessionToken = generateToken();
421
- await ctx.db.insert("agentSessions", {
422
- workspaceId: workspace!._id,
423
- sessionToken,
424
- lastUsedAt: now,
425
- createdAt: now,
426
- });
427
-
428
- return {
429
- success: true,
430
- sessionToken,
431
- provider: {
432
- id: provider!._id,
433
- email: provider!.email,
434
- name: provider!.name,
435
- },
436
- };
437
- },
438
- });
439
-
440
- // Get current session (unified: tries agentSessions first, falls back to legacy sessions)
441
- export const getSession = query({
442
- args: { token: v.string() },
443
- handler: async (ctx, { token }) => {
444
- // 1. Try unified agentSessions (by sessionToken)
445
- const agentSession = await ctx.db
446
- .query("agentSessions")
447
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
448
- .first();
449
-
450
- if (agentSession) {
451
- // Resolve provider via workspace
452
- const provider = await ctx.db
453
- .query("providers")
454
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
455
- .first();
456
-
457
- if (!provider) return null;
458
-
459
- return {
460
- providerId: provider._id,
461
- email: provider.email,
462
- name: provider.name,
463
- stripeOnboardingComplete: (provider as any).stripeOnboardingComplete,
464
- };
465
- }
466
-
467
- // 2. Fallback: legacy sessions table (for tokens created before migration)
468
- const session = await ctx.db
469
- .query("sessions")
470
- .withIndex("by_token", (q) => q.eq("token", token))
471
- .first();
472
-
473
- if (!session || session.expiresAt < Date.now()) {
474
- return null;
475
- }
476
-
477
- const provider = await ctx.db.get(session.providerId);
478
- if (!provider) return null;
479
-
480
- return {
481
- providerId: provider._id,
482
- email: provider.email,
483
- name: provider.name,
484
- stripeOnboardingComplete: (provider as any).stripeOnboardingComplete,
485
- };
486
- },
487
- });
488
-
489
- // ============================================
490
- // DASHBOARD ANALYTICS
491
- // ============================================
492
-
493
- // Get single API by ID
494
- export const getApiById = query({
495
- args: { apiId: v.string() },
496
- handler: async (ctx, args) => {
497
- // Try to get by document ID
498
- try {
499
- const api = await ctx.db.get(args.apiId as any);
500
- if (api) {
501
- // Check if it has Direct Call configured
502
- const directCall = await ctx.db
503
- .query("providerDirectCall")
504
- .filter((q) => q.eq(q.field("apiId"), args.apiId))
505
- .first();
506
- return { ...api, hasDirectCall: !!directCall, directCallStatus: directCall?.status };
507
- }
508
- } catch {
509
- // Not a valid ID format
510
- }
511
- return null;
512
- },
513
- });
514
-
515
- // Get provider APIs with Direct Call status
516
- export const getProviderAPIsWithStatus = query({
517
- args: { providerId: v.string() },
518
- handler: async (ctx, args) => {
519
- const apis = await ctx.db
520
- .query("providerAPIs")
521
- .filter((q) => q.eq(q.field("providerId"), args.providerId as any))
522
- .collect();
523
-
524
- // Add Direct Call status to each API
525
- const apisWithStatus = await Promise.all(
526
- apis.map(async (api) => {
527
- const directCall = await ctx.db
528
- .query("providerDirectCall")
529
- .filter((q) => q.eq(q.field("apiId"), api._id))
530
- .first();
531
- return {
532
- ...api,
533
- hasDirectCall: !!directCall,
534
- directCallStatus: directCall?.status,
535
- };
536
- })
537
- );
538
-
539
- return apisWithStatus;
540
- },
541
- });
542
-
543
- // DEBUG: Delete API
544
- export const debugDeleteAPI = mutation({
545
- args: { apiId: v.string() },
546
- handler: async (ctx, args) => {
547
- await ctx.db.delete(args.apiId as any);
548
- return { deleted: true };
549
- },
550
- });
551
-
552
- // Add API for logged-in provider (used by register page)
553
- export const addAPI = mutation({
554
- args: {
555
- token: v.string(),
556
- api: v.object({
557
- name: v.string(),
558
- description: v.string(),
559
- category: v.string(),
560
- openApiUrl: v.optional(v.string()),
561
- docsUrl: v.optional(v.string()),
562
- pricingModel: v.string(),
563
- pricingNotes: v.optional(v.string()),
564
- }),
565
- },
566
- handler: async (ctx, args) => {
567
- // Unified session lookup
568
- let providerId: any = null;
569
-
570
- const agentSession = await ctx.db
571
- .query("agentSessions")
572
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
573
- .first();
574
-
575
- if (agentSession) {
576
- const prov = await ctx.db
577
- .query("providers")
578
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
579
- .first();
580
- if (prov) providerId = prov._id;
581
- } else {
582
- const session = await ctx.db
583
- .query("sessions")
584
- .withIndex("by_token", (q) => q.eq("token", args.token))
585
- .first();
586
- if (session && session.expiresAt >= Date.now()) {
587
- providerId = session.providerId;
588
- }
589
- }
590
-
591
- if (!providerId) throw new Error("Invalid or expired session");
592
-
593
- const now = Date.now();
594
- const apiId = await ctx.db.insert("providerAPIs", {
595
- providerId,
596
- name: args.api.name,
597
- description: args.api.description,
598
- category: args.api.category,
599
- openApiUrl: args.api.openApiUrl,
600
- docsUrl: args.api.docsUrl,
601
- pricingModel: args.api.pricingModel,
602
- pricingNotes: args.api.pricingNotes,
603
- status: "approved",
604
- createdAt: now,
605
- approvedAt: now,
606
- discoveryCount: 0,
607
- });
608
-
609
- return { apiId, success: true };
610
- },
611
- });
612
-
613
- // Delete API for logged-in provider
614
- export const deleteAPI = mutation({
615
- args: {
616
- token: v.string(),
617
- apiId: v.string(),
618
- },
619
- handler: async (ctx, args) => {
620
- // Unified session lookup
621
- let providerId: any = null;
622
-
623
- const agentSession = await ctx.db
624
- .query("agentSessions")
625
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
626
- .first();
627
-
628
- if (agentSession) {
629
- const prov = await ctx.db
630
- .query("providers")
631
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
632
- .first();
633
- if (prov) providerId = prov._id;
634
- } else {
635
- const session = await ctx.db
636
- .query("sessions")
637
- .withIndex("by_token", (q) => q.eq("token", args.token))
638
- .first();
639
- if (session && session.expiresAt >= Date.now()) {
640
- providerId = session.providerId;
641
- }
642
- }
643
-
644
- if (!providerId) throw new Error("Invalid or expired session");
645
-
646
- // Get the API and verify ownership
647
- const api = await ctx.db.get(args.apiId as any);
648
- if (!api || (api as any).providerId !== providerId) {
649
- throw new Error("API not found or unauthorized");
650
- }
651
-
652
- // Delete the API
653
- await ctx.db.delete(args.apiId as any);
654
-
655
- // Also delete any Direct Call config
656
- const directCallConfig = await ctx.db
657
- .query("providerDirectCall")
658
- .filter((q) => q.eq(q.field("apiId"), args.apiId))
659
- .first();
660
- if (directCallConfig) {
661
- await ctx.db.delete(directCallConfig._id);
662
- }
663
-
664
- return { deleted: true };
665
- },
666
- });
667
-
668
- // DEBUG: Update provider name
669
- export const debugUpdateProvider = mutation({
670
- args: {
671
- providerId: v.string(),
672
- name: v.optional(v.string()),
673
- },
674
- handler: async (ctx, args) => {
675
- const updates: any = {};
676
- if (args.name) updates.name = args.name;
677
- await ctx.db.patch(args.providerId as any, updates);
678
- return { updated: true };
679
- },
680
- });
681
-
682
- // DEBUG: Add API for provider (seeding)
683
- export const debugAddAPI = mutation({
684
- args: {
685
- providerId: v.string(),
686
- name: v.string(),
687
- description: v.string(),
688
- category: v.string(),
689
- docsUrl: v.optional(v.string()),
690
- pricingModel: v.string(),
691
- pricingNotes: v.optional(v.string()),
692
- },
693
- handler: async (ctx, args) => {
694
- const now = Date.now();
695
- return await ctx.db.insert("providerAPIs", {
696
- providerId: args.providerId as any,
697
- name: args.name,
698
- description: args.description,
699
- category: args.category,
700
- docsUrl: args.docsUrl,
701
- pricingModel: args.pricingModel,
702
- pricingNotes: args.pricingNotes,
703
- status: "approved",
704
- createdAt: now,
705
- approvedAt: now,
706
- discoveryCount: 0,
707
- });
708
- },
709
- });
710
-
711
- // DEBUG: Delete provider and all related data
712
- export const debugDeleteProvider = mutation({
713
- args: { providerId: v.string() },
714
- handler: async (ctx, args) => {
715
- const providerId = args.providerId as any;
716
-
717
- // Delete sessions
718
- const sessions = await ctx.db.query("sessions").filter(q => q.eq(q.field("providerId"), providerId)).collect();
719
- for (const s of sessions) await ctx.db.delete(s._id);
720
-
721
- // Delete APIs
722
- const apis = await ctx.db.query("providerAPIs").filter(q => q.eq(q.field("providerId"), providerId)).collect();
723
- for (const a of apis) await ctx.db.delete(a._id);
724
-
725
- // Delete direct call configs
726
- const configs = await ctx.db.query("providerDirectCall").filter(q => q.eq(q.field("providerId"), providerId)).collect();
727
- for (const c of configs) {
728
- // Delete actions for this config
729
- const actions = await ctx.db.query("providerActions").filter(q => q.eq(q.field("directCallId"), c._id)).collect();
730
- for (const act of actions) await ctx.db.delete(act._id);
731
- await ctx.db.delete(c._id);
732
- }
733
-
734
- // Delete provider
735
- await ctx.db.delete(providerId);
736
-
737
- return { deleted: true };
738
- },
739
- });
740
-
741
- // DEBUG: List all sessions
742
- export const debugListSessions = query({
743
- args: {},
744
- handler: async (ctx) => {
745
- return await ctx.db.query("sessions").collect();
746
- },
747
- });
748
-
749
- // DEBUG: List all providers
750
- export const debugListProviders = query({
751
- args: {},
752
- handler: async (ctx) => {
753
- return await ctx.db.query("providers").collect();
754
- },
755
- });
756
-
757
- export const getAnalytics = query({
758
- args: {
759
- token: v.optional(v.string()),
760
- workspaceId: v.optional(v.string()), // Direct workspace ID (used by /workspace page)
761
- period: v.optional(v.string()), // "week", "month", "all"
762
- },
763
- handler: async (ctx, { token, workspaceId: wsIdArg, period = "month" }) => {
764
- let providerId: any = null;
765
-
766
- // Path 1: Direct workspaceId (from workspace page)
767
- if (wsIdArg) {
768
- const prov = await ctx.db
769
- .query("providers")
770
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", wsIdArg as any))
771
- .first();
772
- if (prov) providerId = prov._id;
773
- }
774
-
775
- // Path 2: Session token lookup (from provider dashboard / API)
776
- if (!providerId && token) {
777
- const agentSession = await ctx.db
778
- .query("agentSessions")
779
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
780
- .first();
781
-
782
- if (agentSession) {
783
- const prov = await ctx.db
784
- .query("providers")
785
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
786
- .first();
787
- if (prov) providerId = prov._id;
788
- } else {
789
- const session = await ctx.db
790
- .query("sessions")
791
- .withIndex("by_token", (q) => q.eq("token", token))
792
- .first();
793
- if (session && session.expiresAt >= Date.now()) {
794
- providerId = session.providerId;
795
- }
796
- }
797
- }
798
-
799
- if (!providerId) return null;
800
-
801
- const provider = await ctx.db.get(providerId) as any;
802
- if (!provider) return null;
803
-
804
- const now = Date.now();
805
- const periodMs = {
806
- week: 7 * 24 * 60 * 60 * 1000,
807
- month: 30 * 24 * 60 * 60 * 1000,
808
- all: now,
809
- }[period] || 30 * 24 * 60 * 60 * 1000;
810
-
811
- const startTime = now - periodMs;
812
-
813
- // Provider name key used in apiLogs.provider (lowercase)
814
- const providerKey = (provider.name as string).toLowerCase();
815
-
816
- // Get real data from apiLogs (source of truth for all API activity)
817
- const allLogs = await ctx.db
818
- .query("apiLogs")
819
- .withIndex("by_provider", (q) => q.eq("provider", providerKey))
820
- .collect();
821
-
822
- const periodLogs = allLogs.filter((l) => l.createdAt >= startTime);
823
-
824
- // Split into direct calls vs discovery
825
- const directCalls = periodLogs.filter((l) => !(l as any).action?.startsWith("discovery:"));
826
- const discoveries = periodLogs.filter((l) => (l as any).action?.startsWith("discovery:"));
827
-
828
- // Calculate metrics
829
- const totalCalls = directCalls.length;
830
- const totalDiscoveries = discoveries.length;
831
- const uniqueCallers = new Set(periodLogs.map((l) => (l as any).callerWorkspaceId || l.workspaceId)).size;
832
- const successCount = directCalls.filter((l) => l.status === "success").length;
833
- const successRate = totalCalls > 0 ? (successCount / totalCalls) * 100 : 100;
834
- const avgLatency = totalCalls > 0
835
- ? Math.round(directCalls.reduce((sum, l) => sum + l.latencyMs, 0) / totalCalls)
836
- : 0;
837
-
838
- // Calls over time (daily buckets)
839
- const callsByDay: Record<string, { calls: number; discoveries: number; success: number }> = {};
840
-
841
- periodLogs.forEach((log) => {
842
- const day = new Date(log.createdAt).toISOString().split("T")[0];
843
- if (!callsByDay[day]) {
844
- callsByDay[day] = { calls: 0, discoveries: 0, success: 0 };
845
- }
846
- if ((log as any).action?.startsWith("discovery:")) {
847
- callsByDay[day].discoveries += 1;
848
- } else {
849
- callsByDay[day].calls += 1;
850
- if (log.status === "success") callsByDay[day].success += 1;
851
- }
852
- });
853
-
854
- // Top actions
855
- const actionCallCounts: Record<string, number> = {};
856
- directCalls.forEach((log) => {
857
- const action = (log as any).action || "unknown";
858
- actionCallCounts[action] = (actionCallCounts[action] || 0) + 1;
859
- });
860
- const topActions = Object.entries(actionCallCounts)
861
- .sort((a, b) => b[1] - a[1])
862
- .slice(0, 10)
863
- .map(([actionName, calls]) => ({ actionName, calls }));
864
-
865
- // Top callers (workspace IDs that called this provider)
866
- const callerCounts: Record<string, number> = {};
867
- directCalls.forEach((log) => {
868
- const caller = (log as any).callerWorkspaceId || "anonymous";
869
- callerCounts[caller] = (callerCounts[caller] || 0) + 1;
870
- });
871
- const topAgents = Object.entries(callerCounts)
872
- .sort((a, b) => b[1] - a[1])
873
- .slice(0, 10)
874
- .map(([agentId, calls]) => ({ agentId, calls }));
875
-
876
- // Get provider's APIs
877
- const apis = await ctx.db
878
- .query("providerAPIs")
879
- .withIndex("by_providerId", (q) => q.eq("providerId", providerId))
880
- .collect();
881
-
882
- // Per-API call counts (match action name to API name)
883
- const apiCallCounts: Record<string, number> = {};
884
- const apiDiscoveryCounts: Record<string, number> = {};
885
- for (const api of apis) {
886
- const apiNameLower = api.name.toLowerCase();
887
- apiCallCounts[api._id as string] = directCalls.filter(
888
- (l) => (l as any).action?.toLowerCase().includes(apiNameLower)
889
- ).length;
890
- apiDiscoveryCounts[api._id as string] = discoveries.filter(
891
- (l) => (l as any).action?.toLowerCase().includes(apiNameLower)
892
- ).length;
893
- }
894
-
895
- return {
896
- totalCalls,
897
- totalDiscoveries,
898
- uniqueAgents: uniqueCallers,
899
- totalRevenue: 0, // Revenue tracking not yet implemented
900
- successRate: Math.round(successRate * 10) / 10,
901
- avgLatency,
902
- callsByDay: Object.entries(callsByDay)
903
- .map(([date, data]) => ({
904
- date,
905
- calls: data.calls,
906
- discoveries: data.discoveries,
907
- revenue: 0,
908
- }))
909
- .sort((a, b) => a.date.localeCompare(b.date)),
910
- topAgents,
911
- topActions,
912
- apis: apis.map((api) => ({
913
- id: api._id,
914
- name: api.name,
915
- calls: apiCallCounts[api._id as string] || 0,
916
- discoveries: apiDiscoveryCounts[api._id as string] || 0,
917
- status: api.status,
918
- })),
919
- isPreview: false,
920
- };
921
- },
922
- });
923
-
924
- // ============================================
925
- // DASHBOARD EARNINGS
926
- // ============================================
927
-
928
- // Earnings placeholder - partners (APILayer, Filestack) don't earn per-call revenue yet
929
- export const getEarnings = query({
930
- args: { token: v.string() },
931
- handler: async (ctx, { token }) => {
932
- // Unified session lookup
933
- let providerId: any = null;
934
-
935
- const agentSession = await ctx.db
936
- .query("agentSessions")
937
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
938
- .first();
939
-
940
- if (agentSession) {
941
- const prov = await ctx.db
942
- .query("providers")
943
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
944
- .first();
945
- if (prov) providerId = prov._id;
946
- } else {
947
- const session = await ctx.db
948
- .query("sessions")
949
- .withIndex("by_token", (q) => q.eq("token", token))
950
- .first();
951
- if (session && session.expiresAt >= Date.now()) {
952
- providerId = session.providerId;
953
- }
954
- }
955
-
956
- if (!providerId) return null;
957
-
958
- // Get all payouts (currently empty for all providers)
959
- const payouts = await ctx.db
960
- .query("payouts")
961
- .withIndex("by_providerId", (q) => q.eq("providerId", providerId))
962
- .collect();
963
-
964
- // Get all API calls (legacy table, currently empty)
965
- const allCalls = await ctx.db
966
- .query("apiCalls")
967
- .withIndex("by_providerId", (q) => q.eq("providerId", providerId))
968
- .collect();
969
-
970
- // Find last completed payout
971
- const completedPayouts = payouts
972
- .filter((p) => p.status === "completed")
973
- .sort((a, b) => b.periodEnd - a.periodEnd);
974
-
975
- const lastPayoutEnd = completedPayouts[0]?.periodEnd || 0;
976
-
977
- // Pending = all revenue since last payout
978
- const pendingCalls = allCalls.filter((c) => c.timestamp > lastPayoutEnd);
979
- const pendingAmount = pendingCalls.reduce((sum, c) => sum + c.costUsd, 0);
980
-
981
- // Total earned all time
982
- const totalEarned = allCalls.reduce((sum, c) => sum + c.costUsd, 0);
983
-
984
- // Get provider for Stripe status
985
- const provider = await ctx.db.get(providerId);
986
-
987
- return {
988
- pendingAmount,
989
- totalEarned,
990
- totalPaidOut: completedPayouts.reduce((sum, p) => sum + p.amountUsd, 0),
991
- stripeConnected: !!(provider as any)?.stripeConnectId,
992
- stripeOnboardingComplete: (provider as any)?.stripeOnboardingComplete || false,
993
- payouts: payouts
994
- .sort((a, b) => b.createdAt - a.createdAt)
995
- .slice(0, 20)
996
- .map((p) => ({
997
- id: p._id,
998
- amount: p.amountUsd,
999
- status: p.status,
1000
- periodStart: p.periodStart,
1001
- periodEnd: p.periodEnd,
1002
- createdAt: p.createdAt,
1003
- completedAt: p.completedAt,
1004
- })),
1005
- };
1006
- },
1007
- });
1008
-
1009
- // ============================================
1010
- // ADMIN QUERIES
1011
- // ============================================
1012
-
1013
- // Get all providers (admin only)
1014
- export const getAllProviders = query({
1015
- handler: async (ctx) => {
1016
- return await ctx.db
1017
- .query("providers")
1018
- .order("desc")
1019
- .collect();
1020
- },
1021
- });
1022
-
1023
- // Get all APIs (admin only)
1024
- export const getAllAPIs = query({
1025
- handler: async (ctx) => {
1026
- return await ctx.db
1027
- .query("providerAPIs")
1028
- .order("desc")
1029
- .collect();
1030
- },
1031
- });
1032
-
1033
- // Helper function
1034
- function generateToken(): string {
1035
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1036
- let result = "";
1037
- for (let i = 0; i < 48; i++) {
1038
- result += chars.charAt(Math.floor(Math.random() * chars.length));
1039
- }
1040
- return result;
1041
- }
1042
-
1043
- // Debug: Update API name/description
1044
- export const debugUpdateAPI = mutation({
1045
- args: {
1046
- apiId: v.string(),
1047
- name: v.optional(v.string()),
1048
- description: v.optional(v.string()),
1049
- category: v.optional(v.string()),
1050
- status: v.optional(v.string()),
1051
- hasDirectCall: v.optional(v.boolean()),
1052
- },
1053
- handler: async (ctx, args) => {
1054
- const updates: any = {};
1055
- if (args.name) updates.name = args.name;
1056
- if (args.description) updates.description = args.description;
1057
- if (args.category) updates.category = args.category;
1058
- if (args.status) updates.status = args.status;
1059
- if (args.hasDirectCall !== undefined) updates.hasDirectCall = args.hasDirectCall;
1060
- await ctx.db.patch(args.apiId as any, updates);
1061
- return { updated: true };
1062
- },
1063
- });
1064
-
1065
- // ─── Workspace-native API management (no provider account needed) ─────────────
1066
-
1067
- // Get all APIs listed by a workspace
1068
- export const getByWorkspaceId = query({
1069
- args: { workspaceId: v.id("workspaces") },
1070
- handler: async (ctx, { workspaceId }) => {
1071
- return await ctx.db
1072
- .query("providerAPIs")
1073
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
1074
- .collect();
1075
- },
1076
- });
1077
-
1078
- // List a new API directly from a workspace — no provider registration
1079
- export const createForWorkspace = mutation({
1080
- args: {
1081
- workspaceId: v.id("workspaces"),
1082
- name: v.string(),
1083
- description: v.string(),
1084
- category: v.string(),
1085
- openApiUrl: v.optional(v.string()),
1086
- docsUrl: v.optional(v.string()),
1087
- pricingModel: v.string(),
1088
- pricingNotes: v.optional(v.string()),
1089
- },
1090
- handler: async (ctx, args) => {
1091
- const id = await ctx.db.insert("providerAPIs", {
1092
- workspaceId: args.workspaceId,
1093
- name: args.name,
1094
- description: args.description,
1095
- category: args.category,
1096
- openApiUrl: args.openApiUrl,
1097
- docsUrl: args.docsUrl,
1098
- pricingModel: args.pricingModel,
1099
- pricingNotes: args.pricingNotes,
1100
- status: "active",
1101
- createdAt: Date.now(),
1102
- discoveryCount: 0,
1103
- });
1104
- return { id };
1105
- },
1106
- });
1107
-
1108
- // Delete an API owned by a workspace
1109
- export const deleteForWorkspace = mutation({
1110
- args: { apiId: v.id("providerAPIs"), workspaceId: v.id("workspaces") },
1111
- handler: async (ctx, { apiId, workspaceId }) => {
1112
- const api = await ctx.db.get(apiId);
1113
- if (!api || api.workspaceId !== workspaceId) {
1114
- throw new Error("Not found or unauthorized");
1115
- }
1116
- await ctx.db.delete(apiId);
1117
- return { deleted: true };
1118
- },
1119
- });
1120
-
1121
- // Reset all discoveryCount to 0 (admin cleanup)
1122
- export const resetDiscoveryCounts = mutation({
1123
- args: {},
1124
- handler: async (ctx) => {
1125
- const apis = await ctx.db.query("providerAPIs").collect();
1126
- let reset = 0;
1127
- for (const api of apis) {
1128
- if ((api as any).discoveryCount > 0) {
1129
- await ctx.db.patch(api._id, { discoveryCount: 0 } as any);
1130
- reset++;
1131
- }
1132
- }
1133
- return { reset };
1134
- },
1135
- });