@nordsym/apiclaw 1.8.6 → 1.8.7

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 (125) hide show
  1. package/README.md +4 -4
  2. package/apiclaw-README.md +12 -12
  3. package/convex/_generated/api.d.ts +4 -0
  4. package/convex/apiKeys.ts +220 -0
  5. package/convex/directCall.ts +49 -14
  6. package/convex/email.ts +5 -5
  7. package/convex/http.ts +598 -40
  8. package/convex/logs.ts +26 -22
  9. package/convex/migrateProviderWorkspaces.ts +154 -35
  10. package/convex/providers.ts +313 -203
  11. package/convex/schema.ts +50 -1
  12. package/convex/searchLogs.ts +2 -6
  13. package/convex/seedPratham.ts +1 -1
  14. package/convex/spendAlerts.ts +2 -2
  15. package/convex/stripeActions.ts +1 -1
  16. package/convex/updateAPIStatus.ts +2 -3
  17. package/convex/workspaceSettings.ts +136 -0
  18. package/dist/cli/commands/demo.js +2 -2
  19. package/dist/cli/commands/doctor.js +1 -1
  20. package/dist/cli/commands/login.js +1 -1
  21. package/dist/cli/commands/setup.js +2 -2
  22. package/dist/discovery.js +1 -1
  23. package/dist/execute.js +3 -3
  24. package/dist/index.js +13 -13
  25. package/dist/ui/errors.js +16 -16
  26. package/dist/ui/prompts.js +1 -1
  27. package/email-templates/filestack-provider-outreach.html +1 -1
  28. package/email-templates/partnership-template.html +1 -1
  29. package/email-templates/pratham-partnership-draft.html +2 -2
  30. package/package.json +2 -2
  31. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  32. package/reports/session-report-2026-04-05.html +433 -0
  33. package/src/cli/commands/demo.ts +2 -2
  34. package/src/cli/commands/doctor.ts +1 -1
  35. package/src/cli/commands/login.ts +1 -1
  36. package/src/cli/commands/setup.ts +2 -2
  37. package/src/discovery.ts +1 -1
  38. package/src/execute.ts +3 -3
  39. package/src/index.ts +14 -14
  40. package/src/ui/errors.ts +16 -16
  41. package/src/ui/prompts.ts +1 -1
  42. package/convex/adminActivate.d.ts +0 -3
  43. package/convex/adminActivate.js +0 -47
  44. package/convex/adminStats.d.ts +0 -9
  45. package/convex/adminStats.js +0 -280
  46. package/convex/agents.d.ts +0 -84
  47. package/convex/agents.js +0 -809
  48. package/convex/analytics.d.ts +0 -5
  49. package/convex/analytics.js +0 -167
  50. package/convex/backfillAnalytics.d.ts +0 -2
  51. package/convex/backfillAnalytics.js +0 -20
  52. package/convex/backfillSearchLogs.d.ts +0 -2
  53. package/convex/backfillSearchLogs.js +0 -29
  54. package/convex/billing.d.ts +0 -88
  55. package/convex/billing.js +0 -655
  56. package/convex/capabilities.d.ts +0 -9
  57. package/convex/capabilities.js +0 -145
  58. package/convex/chains.d.ts +0 -68
  59. package/convex/chains.js +0 -1105
  60. package/convex/credits.d.ts +0 -25
  61. package/convex/credits.js +0 -186
  62. package/convex/crons.d.ts +0 -3
  63. package/convex/crons.js +0 -17
  64. package/convex/debugFilestackLogs.d.ts +0 -2
  65. package/convex/debugFilestackLogs.js +0 -17
  66. package/convex/debugGetToken.d.ts +0 -2
  67. package/convex/debugGetToken.js +0 -18
  68. package/convex/directCall.d.ts +0 -72
  69. package/convex/directCall.js +0 -627
  70. package/convex/earnProgress.d.ts +0 -58
  71. package/convex/earnProgress.js +0 -649
  72. package/convex/email.d.ts +0 -14
  73. package/convex/email.js +0 -300
  74. package/convex/feedback.d.ts +0 -7
  75. package/convex/feedback.js +0 -227
  76. package/convex/http.d.ts +0 -3
  77. package/convex/http.js +0 -1408
  78. package/convex/inbound.d.ts +0 -2
  79. package/convex/inbound.js +0 -32
  80. package/convex/logs.d.ts +0 -48
  81. package/convex/logs.js +0 -621
  82. package/convex/migrateFilestack.d.ts +0 -2
  83. package/convex/migrateFilestack.js +0 -74
  84. package/convex/migratePartnersProd.d.ts +0 -8
  85. package/convex/migratePartnersProd.js +0 -165
  86. package/convex/migratePratham.d.ts +0 -2
  87. package/convex/migratePratham.js +0 -121
  88. package/convex/migrateProviderWorkspaces.d.ts +0 -2
  89. package/convex/migrateProviderWorkspaces.js +0 -55
  90. package/convex/mou.d.ts +0 -6
  91. package/convex/mou.js +0 -82
  92. package/convex/providerKeys.d.ts +0 -31
  93. package/convex/providerKeys.js +0 -257
  94. package/convex/providers.d.ts +0 -35
  95. package/convex/providers.js +0 -922
  96. package/convex/purchases.d.ts +0 -7
  97. package/convex/purchases.js +0 -157
  98. package/convex/ratelimit.d.ts +0 -4
  99. package/convex/ratelimit.js +0 -91
  100. package/convex/searchLogs.d.ts +0 -13
  101. package/convex/searchLogs.js +0 -246
  102. package/convex/seedAPILayerAPIs.d.ts +0 -7
  103. package/convex/seedAPILayerAPIs.js +0 -177
  104. package/convex/seedDirectCallConfigs.d.ts +0 -2
  105. package/convex/seedDirectCallConfigs.js +0 -324
  106. package/convex/seedPratham.d.ts +0 -6
  107. package/convex/seedPratham.js +0 -150
  108. package/convex/spendAlerts.d.ts +0 -36
  109. package/convex/spendAlerts.js +0 -380
  110. package/convex/stripeActions.d.ts +0 -19
  111. package/convex/stripeActions.js +0 -411
  112. package/convex/teams.d.ts +0 -21
  113. package/convex/teams.js +0 -215
  114. package/convex/telemetry.d.ts +0 -4
  115. package/convex/telemetry.js +0 -74
  116. package/convex/updateAPIStatus.d.ts +0 -6
  117. package/convex/updateAPIStatus.js +0 -40
  118. package/convex/usage.d.ts +0 -27
  119. package/convex/usage.js +0 -229
  120. package/convex/waitlist.d.ts +0 -4
  121. package/convex/waitlist.js +0 -49
  122. package/convex/webhooks.d.ts +0 -12
  123. package/convex/webhooks.js +0 -410
  124. package/convex/workspaces.d.ts +0 -33
  125. package/convex/workspaces.js +0 -991
@@ -1,922 +0,0 @@
1
- import { mutation, query } from "./_generated/server";
2
- import { v } from "convex/values";
3
- // Register a new provider and their first API
4
- export const registerProvider = mutation({
5
- args: {
6
- provider: v.object({
7
- name: v.string(),
8
- email: v.string(),
9
- website: v.optional(v.string()),
10
- }),
11
- api: v.object({
12
- name: v.string(),
13
- description: v.string(),
14
- category: v.string(),
15
- openApiUrl: v.optional(v.string()),
16
- docsUrl: v.optional(v.string()),
17
- pricingModel: v.string(),
18
- pricingNotes: v.optional(v.string()),
19
- }),
20
- },
21
- handler: async (ctx, args) => {
22
- const now = Date.now();
23
- // Check if provider already exists by email
24
- const existing = await ctx.db
25
- .query("providers")
26
- .withIndex("by_email", (q) => q.eq("email", args.provider.email))
27
- .first();
28
- let providerId;
29
- if (existing) {
30
- // Use existing provider
31
- providerId = existing._id;
32
- }
33
- else {
34
- // Create new provider - auto-approve for now
35
- providerId = await ctx.db.insert("providers", {
36
- name: args.provider.name,
37
- email: args.provider.email,
38
- website: args.provider.website,
39
- status: "approved", // Auto-approve for MVP
40
- createdAt: now,
41
- updatedAt: now,
42
- approvedAt: now,
43
- });
44
- }
45
- // Create the API listing - auto-approve for now
46
- const apiId = await ctx.db.insert("providerAPIs", {
47
- providerId,
48
- name: args.api.name,
49
- description: args.api.description,
50
- category: args.api.category,
51
- openApiUrl: args.api.openApiUrl,
52
- docsUrl: args.api.docsUrl,
53
- pricingModel: args.api.pricingModel,
54
- pricingNotes: args.api.pricingNotes,
55
- status: "approved", // Auto-approve for MVP
56
- createdAt: now,
57
- approvedAt: now,
58
- discoveryCount: 0,
59
- });
60
- // Create session for auto-login after registration
61
- const sessionToken = generateToken();
62
- const sessionExpiresAt = now + 30 * 24 * 60 * 60 * 1000; // 30 days
63
- await ctx.db.insert("sessions", {
64
- providerId,
65
- token: sessionToken,
66
- expiresAt: sessionExpiresAt,
67
- createdAt: now,
68
- });
69
- return { providerId, apiId, sessionToken };
70
- },
71
- });
72
- // Get provider by email
73
- export const getProviderByEmail = query({
74
- args: { email: v.string() },
75
- handler: async (ctx, args) => {
76
- return await ctx.db
77
- .query("providers")
78
- .withIndex("by_email", (q) => q.eq("email", args.email))
79
- .first();
80
- },
81
- });
82
- // Get all APIs for a provider
83
- export const getProviderAPIs = query({
84
- args: { providerId: v.id("providers") },
85
- handler: async (ctx, args) => {
86
- return await ctx.db
87
- .query("providerAPIs")
88
- .withIndex("by_providerId", (q) => q.eq("providerId", args.providerId))
89
- .collect();
90
- },
91
- });
92
- // Get all approved APIs (for the registry)
93
- export const getApprovedAPIs = query({
94
- args: {
95
- category: v.optional(v.string()),
96
- limit: v.optional(v.number()),
97
- },
98
- handler: async (ctx, args) => {
99
- const query = ctx.db
100
- .query("providerAPIs")
101
- .withIndex("by_status", (q) => q.eq("status", "approved"));
102
- const apis = await query.collect();
103
- // Filter by category if provided
104
- let filtered = args.category
105
- ? apis.filter((api) => api.category === args.category)
106
- : apis;
107
- // Apply limit
108
- if (args.limit) {
109
- filtered = filtered.slice(0, args.limit);
110
- }
111
- return filtered;
112
- },
113
- });
114
- // Get API categories with counts
115
- export const getCategories = query({
116
- handler: async (ctx) => {
117
- const apis = await ctx.db
118
- .query("providerAPIs")
119
- .withIndex("by_status", (q) => q.eq("status", "approved"))
120
- .collect();
121
- const categories = {};
122
- for (const api of apis) {
123
- categories[api.category] = (categories[api.category] || 0) + 1;
124
- }
125
- return Object.entries(categories)
126
- .map(([name, count]) => ({ name, count }))
127
- .sort((a, b) => b.count - a.count);
128
- },
129
- });
130
- // Increment discovery count when an agent finds an API
131
- export const trackDiscovery = mutation({
132
- args: { apiId: v.id("providerAPIs") },
133
- handler: async (ctx, args) => {
134
- const api = await ctx.db.get(args.apiId);
135
- if (!api)
136
- return;
137
- await ctx.db.patch(args.apiId, {
138
- discoveryCount: (api.discoveryCount || 0) + 1,
139
- lastDiscoveredAt: Date.now(),
140
- });
141
- },
142
- });
143
- // Unified discovery logging
144
- // Single source of truth: apiLogs. discoveryCount on My APIs is derived from apiLogs.
145
- export const logDiscovery = mutation({
146
- args: {
147
- provider: v.string(),
148
- query: v.string(),
149
- latencyMs: v.number(),
150
- callerWorkspaceId: v.string(),
151
- },
152
- handler: async (ctx, args) => {
153
- const providerEmailMap = {
154
- apilayer: "pratham.kumar@apilayer.com",
155
- filestack: "marketing@filestack.com",
156
- };
157
- const email = providerEmailMap[args.provider];
158
- if (!email)
159
- return { logged: false };
160
- const workspace = await ctx.db
161
- .query("workspaces")
162
- .withIndex("by_email", (q) => q.eq("email", email))
163
- .first();
164
- if (!workspace)
165
- return { logged: false };
166
- // 1. Log to apiLogs (source of truth for Analytics)
167
- await ctx.db.insert("apiLogs", {
168
- workspaceId: workspace._id,
169
- sessionToken: "",
170
- provider: args.provider,
171
- action: `discovery:${args.query}`,
172
- status: "success",
173
- latencyMs: args.latencyMs,
174
- direction: "inbound",
175
- callerWorkspaceId: args.callerWorkspaceId,
176
- createdAt: Date.now(),
177
- });
178
- // 2. Increment discoveryCount on MATCHING APIs only
179
- const providerRecord = await ctx.db
180
- .query("providers")
181
- .withIndex("by_email", (q) => q.eq("email", email))
182
- .first();
183
- if (providerRecord) {
184
- const apis = await ctx.db.query("providerAPIs").collect();
185
- const providerApis = apis.filter((a) => a.providerId === providerRecord._id);
186
- const queryLower = args.query.toLowerCase();
187
- const queryWords = queryLower.split(/\s+/).filter((w) => w.length > 2);
188
- let matched = 0;
189
- for (const api of providerApis) {
190
- const apiText = `${api.name} ${api.description || ""}`.toLowerCase();
191
- if (queryWords.some((w) => apiText.includes(w))) {
192
- await ctx.db.patch(api._id, {
193
- discoveryCount: (api.discoveryCount || 0) + 1,
194
- lastDiscoveredAt: Date.now(),
195
- });
196
- matched++;
197
- }
198
- }
199
- return { logged: true, matched };
200
- }
201
- return { logged: true, matched: 0 };
202
- },
203
- });
204
- // Legacy: Track discovery by provider name (kept for backwards compat)
205
- export const trackDiscoveryByProvider = mutation({
206
- args: { provider: v.string(), query: v.string() },
207
- handler: async (ctx, args) => {
208
- // Find provider by email mapping
209
- const providerEmailMap = {
210
- apilayer: "pratham.kumar@apilayer.com",
211
- filestack: "marketing@filestack.com",
212
- };
213
- const email = providerEmailMap[args.provider];
214
- if (!email)
215
- return { updated: 0, error: "no email mapping" };
216
- // Find provider record by email
217
- const provider = await ctx.db
218
- .query("providers")
219
- .withIndex("by_email", (q) => q.eq("email", email))
220
- .first();
221
- if (!provider)
222
- return { updated: 0, error: "provider not found" };
223
- // Get all APIs for this provider
224
- const apis = await ctx.db.query("providerAPIs").collect();
225
- const providerApis = apis.filter((a) => a.providerId === provider._id);
226
- // Increment discoveryCount on ALL provider APIs
227
- for (const api of providerApis) {
228
- await ctx.db.patch(api._id, {
229
- discoveryCount: (api.discoveryCount || 0) + 1,
230
- lastDiscoveredAt: Date.now(),
231
- });
232
- }
233
- return { updated: providerApis.length };
234
- },
235
- });
236
- // Admin: List pending providers
237
- export const getPendingProviders = query({
238
- handler: async (ctx) => {
239
- return await ctx.db
240
- .query("providers")
241
- .withIndex("by_status", (q) => q.eq("status", "pending"))
242
- .collect();
243
- },
244
- });
245
- // Admin: Approve provider
246
- export const approveProvider = mutation({
247
- args: { providerId: v.id("providers") },
248
- handler: async (ctx, args) => {
249
- await ctx.db.patch(args.providerId, {
250
- status: "approved",
251
- approvedAt: Date.now(),
252
- updatedAt: Date.now(),
253
- });
254
- },
255
- });
256
- // Admin: Reject provider
257
- export const rejectProvider = mutation({
258
- args: { providerId: v.id("providers") },
259
- handler: async (ctx, args) => {
260
- await ctx.db.patch(args.providerId, {
261
- status: "rejected",
262
- updatedAt: Date.now(),
263
- });
264
- },
265
- });
266
- // Get provider stats
267
- export const getProviderStats = query({
268
- handler: async (ctx) => {
269
- const providers = await ctx.db.query("providers").collect();
270
- const apis = await ctx.db.query("providerAPIs").collect();
271
- return {
272
- totalProviders: providers.length,
273
- approvedProviders: providers.filter((p) => p.status === "approved").length,
274
- pendingProviders: providers.filter((p) => p.status === "pending").length,
275
- totalAPIs: apis.length,
276
- approvedAPIs: apis.filter((a) => a.status === "approved").length,
277
- pendingAPIs: apis.filter((a) => a.status === "pending").length,
278
- totalDiscoveries: apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0),
279
- };
280
- },
281
- });
282
- // ============================================
283
- // DASHBOARD AUTH & SESSION FUNCTIONS
284
- // ============================================
285
- // Create magic link for email auth
286
- export const createMagicLink = mutation({
287
- args: { email: v.string() },
288
- handler: async (ctx, { email }) => {
289
- const token = generateToken();
290
- const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes
291
- await ctx.db.insert("magicLinks", {
292
- email: email.toLowerCase(),
293
- token,
294
- expiresAt,
295
- createdAt: Date.now(),
296
- });
297
- return { token, expiresAt };
298
- },
299
- });
300
- // Verify magic link and create session
301
- export const verifyMagicLink = mutation({
302
- args: { token: v.string() },
303
- handler: async (ctx, { token }) => {
304
- const magicLink = await ctx.db
305
- .query("magicLinks")
306
- .withIndex("by_token", (q) => q.eq("token", token))
307
- .first();
308
- if (!magicLink) {
309
- return { success: false, error: "Invalid token" };
310
- }
311
- if (magicLink.expiresAt < Date.now()) {
312
- return { success: false, error: "Token expired" };
313
- }
314
- if (magicLink.usedAt) {
315
- return { success: false, error: "Token already used" };
316
- }
317
- // Mark as used
318
- await ctx.db.patch(magicLink._id, { usedAt: Date.now() });
319
- // Find or create provider
320
- let provider = await ctx.db
321
- .query("providers")
322
- .withIndex("by_email", (q) => q.eq("email", magicLink.email))
323
- .first();
324
- if (!provider) {
325
- const providerId = await ctx.db.insert("providers", {
326
- email: magicLink.email,
327
- name: magicLink.email.split("@")[0],
328
- status: "approved",
329
- createdAt: Date.now(),
330
- updatedAt: Date.now(),
331
- });
332
- provider = await ctx.db.get(providerId);
333
- }
334
- // Create session
335
- const sessionToken = generateToken();
336
- const sessionExpiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000; // 30 days
337
- await ctx.db.insert("sessions", {
338
- providerId: provider._id,
339
- token: sessionToken,
340
- expiresAt: sessionExpiresAt,
341
- createdAt: Date.now(),
342
- });
343
- return {
344
- success: true,
345
- sessionToken,
346
- provider: {
347
- id: provider._id,
348
- email: provider.email,
349
- name: provider.name,
350
- },
351
- };
352
- },
353
- });
354
- // Get current session
355
- export const getSession = query({
356
- args: { token: v.string() },
357
- handler: async (ctx, { token }) => {
358
- const session = await ctx.db
359
- .query("sessions")
360
- .withIndex("by_token", (q) => q.eq("token", token))
361
- .first();
362
- if (!session || session.expiresAt < Date.now()) {
363
- return null;
364
- }
365
- const provider = await ctx.db.get(session.providerId);
366
- if (!provider)
367
- return null;
368
- return {
369
- providerId: provider._id,
370
- email: provider.email,
371
- name: provider.name,
372
- stripeOnboardingComplete: provider.stripeOnboardingComplete,
373
- };
374
- },
375
- });
376
- // ============================================
377
- // DASHBOARD ANALYTICS
378
- // ============================================
379
- // Get single API by ID
380
- export const getApiById = query({
381
- args: { apiId: v.string() },
382
- handler: async (ctx, args) => {
383
- // Try to get by document ID
384
- try {
385
- const api = await ctx.db.get(args.apiId);
386
- if (api) {
387
- // Check if it has Direct Call configured
388
- const directCall = await ctx.db
389
- .query("providerDirectCall")
390
- .filter((q) => q.eq(q.field("apiId"), args.apiId))
391
- .first();
392
- return { ...api, hasDirectCall: !!directCall, directCallStatus: directCall?.status };
393
- }
394
- }
395
- catch {
396
- // Not a valid ID format
397
- }
398
- return null;
399
- },
400
- });
401
- // Get provider APIs with Direct Call status
402
- export const getProviderAPIsWithStatus = query({
403
- args: { providerId: v.string() },
404
- handler: async (ctx, args) => {
405
- const apis = await ctx.db
406
- .query("providerAPIs")
407
- .filter((q) => q.eq(q.field("providerId"), args.providerId))
408
- .collect();
409
- // Add Direct Call status to each API
410
- const apisWithStatus = await Promise.all(apis.map(async (api) => {
411
- const directCall = await ctx.db
412
- .query("providerDirectCall")
413
- .filter((q) => q.eq(q.field("apiId"), api._id))
414
- .first();
415
- return {
416
- ...api,
417
- hasDirectCall: !!directCall,
418
- directCallStatus: directCall?.status,
419
- };
420
- }));
421
- return apisWithStatus;
422
- },
423
- });
424
- // DEBUG: Delete API
425
- export const debugDeleteAPI = mutation({
426
- args: { apiId: v.string() },
427
- handler: async (ctx, args) => {
428
- await ctx.db.delete(args.apiId);
429
- return { deleted: true };
430
- },
431
- });
432
- // Add API for logged-in provider (used by register page)
433
- export const addAPI = mutation({
434
- args: {
435
- token: v.string(),
436
- api: v.object({
437
- name: v.string(),
438
- description: v.string(),
439
- category: v.string(),
440
- openApiUrl: v.optional(v.string()),
441
- docsUrl: v.optional(v.string()),
442
- pricingModel: v.string(),
443
- pricingNotes: v.optional(v.string()),
444
- }),
445
- },
446
- handler: async (ctx, args) => {
447
- // Verify session
448
- const session = await ctx.db
449
- .query("sessions")
450
- .withIndex("by_token", (q) => q.eq("token", args.token))
451
- .first();
452
- if (!session || session.expiresAt < Date.now()) {
453
- throw new Error("Invalid or expired session");
454
- }
455
- const now = Date.now();
456
- const apiId = await ctx.db.insert("providerAPIs", {
457
- providerId: session.providerId,
458
- name: args.api.name,
459
- description: args.api.description,
460
- category: args.api.category,
461
- openApiUrl: args.api.openApiUrl,
462
- docsUrl: args.api.docsUrl,
463
- pricingModel: args.api.pricingModel,
464
- pricingNotes: args.api.pricingNotes,
465
- status: "approved",
466
- createdAt: now,
467
- approvedAt: now,
468
- discoveryCount: 0,
469
- });
470
- return { apiId, success: true };
471
- },
472
- });
473
- // Delete API for logged-in provider
474
- export const deleteAPI = mutation({
475
- args: {
476
- token: v.string(),
477
- apiId: v.string(),
478
- },
479
- handler: async (ctx, args) => {
480
- // Verify session
481
- const session = await ctx.db
482
- .query("sessions")
483
- .withIndex("by_token", (q) => q.eq("token", args.token))
484
- .first();
485
- if (!session || session.expiresAt < Date.now()) {
486
- throw new Error("Invalid or expired session");
487
- }
488
- // Get the API and verify ownership
489
- const api = await ctx.db.get(args.apiId);
490
- if (!api || api.providerId !== session.providerId) {
491
- throw new Error("API not found or unauthorized");
492
- }
493
- // Delete the API
494
- await ctx.db.delete(args.apiId);
495
- // Also delete any Direct Call config
496
- const directCallConfig = await ctx.db
497
- .query("providerDirectCall")
498
- .filter((q) => q.eq(q.field("apiId"), args.apiId))
499
- .first();
500
- if (directCallConfig) {
501
- await ctx.db.delete(directCallConfig._id);
502
- }
503
- return { deleted: true };
504
- },
505
- });
506
- // DEBUG: Update provider name
507
- export const debugUpdateProvider = mutation({
508
- args: {
509
- providerId: v.string(),
510
- name: v.optional(v.string()),
511
- },
512
- handler: async (ctx, args) => {
513
- const updates = {};
514
- if (args.name)
515
- updates.name = args.name;
516
- await ctx.db.patch(args.providerId, updates);
517
- return { updated: true };
518
- },
519
- });
520
- // DEBUG: Add API for provider (seeding)
521
- export const debugAddAPI = mutation({
522
- args: {
523
- providerId: v.string(),
524
- name: v.string(),
525
- description: v.string(),
526
- category: v.string(),
527
- docsUrl: v.optional(v.string()),
528
- pricingModel: v.string(),
529
- pricingNotes: v.optional(v.string()),
530
- },
531
- handler: async (ctx, args) => {
532
- const now = Date.now();
533
- return await ctx.db.insert("providerAPIs", {
534
- providerId: args.providerId,
535
- name: args.name,
536
- description: args.description,
537
- category: args.category,
538
- docsUrl: args.docsUrl,
539
- pricingModel: args.pricingModel,
540
- pricingNotes: args.pricingNotes,
541
- status: "approved",
542
- createdAt: now,
543
- approvedAt: now,
544
- discoveryCount: 0,
545
- });
546
- },
547
- });
548
- // DEBUG: Delete provider and all related data
549
- export const debugDeleteProvider = mutation({
550
- args: { providerId: v.string() },
551
- handler: async (ctx, args) => {
552
- const providerId = args.providerId;
553
- // Delete sessions
554
- const sessions = await ctx.db.query("sessions").filter(q => q.eq(q.field("providerId"), providerId)).collect();
555
- for (const s of sessions)
556
- await ctx.db.delete(s._id);
557
- // Delete APIs
558
- const apis = await ctx.db.query("providerAPIs").filter(q => q.eq(q.field("providerId"), providerId)).collect();
559
- for (const a of apis)
560
- await ctx.db.delete(a._id);
561
- // Delete direct call configs
562
- const configs = await ctx.db.query("providerDirectCall").filter(q => q.eq(q.field("providerId"), providerId)).collect();
563
- for (const c of configs) {
564
- // Delete actions for this config
565
- const actions = await ctx.db.query("providerActions").filter(q => q.eq(q.field("directCallId"), c._id)).collect();
566
- for (const act of actions)
567
- await ctx.db.delete(act._id);
568
- await ctx.db.delete(c._id);
569
- }
570
- // Delete provider
571
- await ctx.db.delete(providerId);
572
- return { deleted: true };
573
- },
574
- });
575
- // DEBUG: List all sessions
576
- export const debugListSessions = query({
577
- args: {},
578
- handler: async (ctx) => {
579
- return await ctx.db.query("sessions").collect();
580
- },
581
- });
582
- // DEBUG: List all providers
583
- export const debugListProviders = query({
584
- args: {},
585
- handler: async (ctx) => {
586
- return await ctx.db.query("providers").collect();
587
- },
588
- });
589
- export const getAnalytics = query({
590
- args: {
591
- token: v.string(),
592
- period: v.optional(v.string()), // "week", "month", "all"
593
- },
594
- handler: async (ctx, { token, period = "month" }) => {
595
- const session = await ctx.db
596
- .query("sessions")
597
- .withIndex("by_token", (q) => q.eq("token", token))
598
- .first();
599
- if (!session || session.expiresAt < Date.now()) {
600
- return null;
601
- }
602
- const now = Date.now();
603
- const periodMs = {
604
- week: 7 * 24 * 60 * 60 * 1000,
605
- month: 30 * 24 * 60 * 60 * 1000,
606
- all: now,
607
- }[period] || 30 * 24 * 60 * 60 * 1000;
608
- const startTime = now - periodMs;
609
- // Get usage logs for this provider (from Direct Call usageLog)
610
- const usageLogs = await ctx.db
611
- .query("usageLog")
612
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
613
- .collect();
614
- const periodCalls = usageLogs.filter((c) => c.timestamp >= startTime);
615
- // Calculate metrics
616
- const totalCalls = periodCalls.length;
617
- const uniqueAgents = new Set(periodCalls.map((c) => c.userId)).size;
618
- const totalRevenue = periodCalls.reduce((sum, c) => sum + (c.creditsUsed / 100), 0); // cents to dollars
619
- const successCount = periodCalls.filter((c) => c.success).length;
620
- const successRate = totalCalls > 0 ? (successCount / totalCalls) * 100 : 100;
621
- const avgLatency = totalCalls > 0
622
- ? periodCalls.reduce((sum, c) => sum + c.latencyMs, 0) / totalCalls
623
- : 0;
624
- // Calls over time (daily buckets)
625
- const callsByDay = {};
626
- periodCalls.forEach((call) => {
627
- const day = new Date(call.timestamp).toISOString().split("T")[0];
628
- if (!callsByDay[day]) {
629
- callsByDay[day] = { calls: 0, revenue: 0, success: 0 };
630
- }
631
- callsByDay[day].calls += 1;
632
- callsByDay[day].revenue += call.creditsUsed / 100;
633
- if (call.success)
634
- callsByDay[day].success += 1;
635
- });
636
- // Top agents (users)
637
- const agentCallCounts = {};
638
- periodCalls.forEach((call) => {
639
- agentCallCounts[call.userId] = (agentCallCounts[call.userId] || 0) + 1;
640
- });
641
- const topAgents = Object.entries(agentCallCounts)
642
- .sort((a, b) => b[1] - a[1])
643
- .slice(0, 10)
644
- .map(([agentId, calls]) => ({ agentId, calls }));
645
- // Top actions
646
- const actionCallCounts = {};
647
- periodCalls.forEach((call) => {
648
- actionCallCounts[call.actionName] = (actionCallCounts[call.actionName] || 0) + 1;
649
- });
650
- const topActions = Object.entries(actionCallCounts)
651
- .sort((a, b) => b[1] - a[1])
652
- .slice(0, 10)
653
- .map(([actionName, calls]) => ({ actionName, calls }));
654
- // Get provider's APIs
655
- const apis = await ctx.db
656
- .query("providerAPIs")
657
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
658
- .collect();
659
- // Get Direct Call configs to map directCallId to apiId
660
- const directCallConfigs = await ctx.db
661
- .query("providerDirectCall")
662
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
663
- .collect();
664
- // Calls per API (via directCallId → apiId mapping)
665
- const callsByDirectCallId = {};
666
- periodCalls.forEach((call) => {
667
- const dcId = call.directCallId;
668
- callsByDirectCallId[dcId] = (callsByDirectCallId[dcId] || 0) + 1;
669
- });
670
- // Map to apiId
671
- const callsByApiId = {};
672
- directCallConfigs.forEach((dc) => {
673
- if (dc.apiId) {
674
- callsByApiId[dc.apiId] = callsByDirectCallId[dc._id] || 0;
675
- }
676
- });
677
- // Preview data for providers with no usage yet
678
- const isPreview = totalCalls === 0;
679
- if (isPreview) {
680
- // Generate preview data so providers can see what the dashboard looks like
681
- const previewDays = [];
682
- for (let i = 13; i >= 0; i--) {
683
- const date = new Date(now - i * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
684
- previewDays.push({
685
- date,
686
- calls: Math.floor(Math.random() * 50) + 10,
687
- revenue: Math.random() * 5,
688
- });
689
- }
690
- return {
691
- totalCalls: 847,
692
- uniqueAgents: 23,
693
- totalRevenue: 42.35,
694
- successRate: 98.2,
695
- avgLatency: 145,
696
- callsByDay: previewDays,
697
- topAgents: [
698
- { agentId: "agent_demo_1", calls: 234 },
699
- { agentId: "agent_demo_2", calls: 189 },
700
- { agentId: "agent_demo_3", calls: 156 },
701
- { agentId: "agent_demo_4", calls: 98 },
702
- { agentId: "agent_demo_5", calls: 67 },
703
- ],
704
- topActions: [
705
- { actionName: "send_message", calls: 412 },
706
- { actionName: "get_status", calls: 289 },
707
- { actionName: "create_invoice", calls: 146 },
708
- ],
709
- apis: apis.map((api) => ({
710
- id: api._id,
711
- name: api.name,
712
- calls: Math.floor(Math.random() * 200) + 50,
713
- status: api.status,
714
- })),
715
- isPreview: true,
716
- };
717
- }
718
- return {
719
- totalCalls,
720
- uniqueAgents,
721
- totalRevenue,
722
- successRate,
723
- avgLatency,
724
- callsByDay: Object.entries(callsByDay)
725
- .map(([date, data]) => ({
726
- date,
727
- calls: data.calls,
728
- revenue: data.revenue,
729
- }))
730
- .sort((a, b) => a.date.localeCompare(b.date)),
731
- topAgents,
732
- topActions,
733
- apis: apis.map((api) => ({
734
- id: api._id,
735
- name: api.name,
736
- calls: callsByApiId[api._id] || 0,
737
- status: api.status,
738
- })),
739
- isPreview: false,
740
- };
741
- },
742
- });
743
- // ============================================
744
- // DASHBOARD EARNINGS
745
- // ============================================
746
- export const getEarnings = query({
747
- args: { token: v.string() },
748
- handler: async (ctx, { token }) => {
749
- const session = await ctx.db
750
- .query("sessions")
751
- .withIndex("by_token", (q) => q.eq("token", token))
752
- .first();
753
- if (!session || session.expiresAt < Date.now()) {
754
- return null;
755
- }
756
- // Get all payouts
757
- const payouts = await ctx.db
758
- .query("payouts")
759
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
760
- .collect();
761
- // Get all API calls to calculate pending
762
- const allCalls = await ctx.db
763
- .query("apiCalls")
764
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
765
- .collect();
766
- // Find last completed payout
767
- const completedPayouts = payouts
768
- .filter((p) => p.status === "completed")
769
- .sort((a, b) => b.periodEnd - a.periodEnd);
770
- const lastPayoutEnd = completedPayouts[0]?.periodEnd || 0;
771
- // Pending = all revenue since last payout
772
- const pendingCalls = allCalls.filter((c) => c.timestamp > lastPayoutEnd);
773
- const pendingAmount = pendingCalls.reduce((sum, c) => sum + c.costUsd, 0);
774
- // Total earned all time
775
- const totalEarned = allCalls.reduce((sum, c) => sum + c.costUsd, 0);
776
- // Get provider for Stripe status
777
- const provider = await ctx.db.get(session.providerId);
778
- return {
779
- pendingAmount,
780
- totalEarned,
781
- totalPaidOut: completedPayouts.reduce((sum, p) => sum + p.amountUsd, 0),
782
- stripeConnected: !!provider?.stripeConnectId,
783
- stripeOnboardingComplete: provider?.stripeOnboardingComplete || false,
784
- payouts: payouts
785
- .sort((a, b) => b.createdAt - a.createdAt)
786
- .slice(0, 20)
787
- .map((p) => ({
788
- id: p._id,
789
- amount: p.amountUsd,
790
- status: p.status,
791
- periodStart: p.periodStart,
792
- periodEnd: p.periodEnd,
793
- createdAt: p.createdAt,
794
- completedAt: p.completedAt,
795
- })),
796
- };
797
- },
798
- });
799
- // ============================================
800
- // ADMIN QUERIES
801
- // ============================================
802
- // Get all providers (admin only)
803
- export const getAllProviders = query({
804
- handler: async (ctx) => {
805
- return await ctx.db
806
- .query("providers")
807
- .order("desc")
808
- .collect();
809
- },
810
- });
811
- // Get all APIs (admin only)
812
- export const getAllAPIs = query({
813
- handler: async (ctx) => {
814
- return await ctx.db
815
- .query("providerAPIs")
816
- .order("desc")
817
- .collect();
818
- },
819
- });
820
- // Helper function
821
- function generateToken() {
822
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
823
- let result = "";
824
- for (let i = 0; i < 48; i++) {
825
- result += chars.charAt(Math.floor(Math.random() * chars.length));
826
- }
827
- return result;
828
- }
829
- // Debug: Update API name/description
830
- export const debugUpdateAPI = mutation({
831
- args: {
832
- apiId: v.string(),
833
- name: v.optional(v.string()),
834
- description: v.optional(v.string()),
835
- category: v.optional(v.string()),
836
- status: v.optional(v.string()),
837
- hasDirectCall: v.optional(v.boolean()),
838
- },
839
- handler: async (ctx, args) => {
840
- const updates = {};
841
- if (args.name)
842
- updates.name = args.name;
843
- if (args.description)
844
- updates.description = args.description;
845
- if (args.category)
846
- updates.category = args.category;
847
- if (args.status)
848
- updates.status = args.status;
849
- if (args.hasDirectCall !== undefined)
850
- updates.hasDirectCall = args.hasDirectCall;
851
- await ctx.db.patch(args.apiId, updates);
852
- return { updated: true };
853
- },
854
- });
855
- // ─── Workspace-native API management (no provider account needed) ─────────────
856
- // Get all APIs listed by a workspace
857
- export const getByWorkspaceId = query({
858
- args: { workspaceId: v.id("workspaces") },
859
- handler: async (ctx, { workspaceId }) => {
860
- return await ctx.db
861
- .query("providerAPIs")
862
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
863
- .collect();
864
- },
865
- });
866
- // List a new API directly from a workspace — no provider registration
867
- export const createForWorkspace = mutation({
868
- args: {
869
- workspaceId: v.id("workspaces"),
870
- name: v.string(),
871
- description: v.string(),
872
- category: v.string(),
873
- openApiUrl: v.optional(v.string()),
874
- docsUrl: v.optional(v.string()),
875
- pricingModel: v.string(),
876
- pricingNotes: v.optional(v.string()),
877
- },
878
- handler: async (ctx, args) => {
879
- const id = await ctx.db.insert("providerAPIs", {
880
- workspaceId: args.workspaceId,
881
- name: args.name,
882
- description: args.description,
883
- category: args.category,
884
- openApiUrl: args.openApiUrl,
885
- docsUrl: args.docsUrl,
886
- pricingModel: args.pricingModel,
887
- pricingNotes: args.pricingNotes,
888
- status: "active",
889
- createdAt: Date.now(),
890
- discoveryCount: 0,
891
- });
892
- return { id };
893
- },
894
- });
895
- // Delete an API owned by a workspace
896
- export const deleteForWorkspace = mutation({
897
- args: { apiId: v.id("providerAPIs"), workspaceId: v.id("workspaces") },
898
- handler: async (ctx, { apiId, workspaceId }) => {
899
- const api = await ctx.db.get(apiId);
900
- if (!api || api.workspaceId !== workspaceId) {
901
- throw new Error("Not found or unauthorized");
902
- }
903
- await ctx.db.delete(apiId);
904
- return { deleted: true };
905
- },
906
- });
907
- // Reset all discoveryCount to 0 (admin cleanup)
908
- export const resetDiscoveryCounts = mutation({
909
- args: {},
910
- handler: async (ctx) => {
911
- const apis = await ctx.db.query("providerAPIs").collect();
912
- let reset = 0;
913
- for (const api of apis) {
914
- if (api.discoveryCount > 0) {
915
- await ctx.db.patch(api._id, { discoveryCount: 0 });
916
- reset++;
917
- }
918
- }
919
- return { reset };
920
- },
921
- });
922
- //# sourceMappingURL=providers.js.map