@nordsym/apiclaw 1.8.6 → 1.8.8

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 (115) hide show
  1. package/README.md +59 -31
  2. package/apiclaw-README.md +12 -12
  3. package/convex/_generated/api.d.ts +4 -0
  4. package/convex/adminActivate.d.ts.map +1 -1
  5. package/convex/adminActivate.js +1 -2
  6. package/convex/adminActivate.js.map +1 -1
  7. package/convex/adminActivate.ts +1 -2
  8. package/convex/adminStats.d.ts.map +1 -1
  9. package/convex/adminStats.js +5 -3
  10. package/convex/adminStats.js.map +1 -1
  11. package/convex/adminStats.ts +5 -3
  12. package/convex/apiKeys.d.ts +6 -0
  13. package/convex/apiKeys.d.ts.map +1 -0
  14. package/convex/apiKeys.js +186 -0
  15. package/convex/apiKeys.js.map +1 -0
  16. package/convex/apiKeys.ts +220 -0
  17. package/convex/billing.d.ts.map +1 -1
  18. package/convex/billing.js +1 -13
  19. package/convex/billing.js.map +1 -1
  20. package/convex/billing.ts +2 -14
  21. package/convex/directCall.d.ts.map +1 -1
  22. package/convex/directCall.js +49 -13
  23. package/convex/directCall.js.map +1 -1
  24. package/convex/directCall.ts +49 -14
  25. package/convex/email.js +5 -5
  26. package/convex/email.js.map +1 -1
  27. package/convex/email.ts +5 -5
  28. package/convex/http.d.ts.map +1 -1
  29. package/convex/http.js +756 -29
  30. package/convex/http.js.map +1 -1
  31. package/convex/http.ts +870 -40
  32. package/convex/logs.d.ts.map +1 -1
  33. package/convex/logs.js +21 -19
  34. package/convex/logs.js.map +1 -1
  35. package/convex/logs.ts +26 -22
  36. package/convex/migrateProviderWorkspaces.d.ts +11 -0
  37. package/convex/migrateProviderWorkspaces.d.ts.map +1 -1
  38. package/convex/migrateProviderWorkspaces.js +120 -34
  39. package/convex/migrateProviderWorkspaces.js.map +1 -1
  40. package/convex/migrateProviderWorkspaces.ts +154 -35
  41. package/convex/providers.d.ts.map +1 -1
  42. package/convex/providers.js +296 -191
  43. package/convex/providers.js.map +1 -1
  44. package/convex/providers.ts +313 -203
  45. package/convex/schema.ts +54 -5
  46. package/convex/searchLogs.d.ts.map +1 -1
  47. package/convex/searchLogs.js +2 -7
  48. package/convex/searchLogs.js.map +1 -1
  49. package/convex/searchLogs.ts +2 -6
  50. package/convex/seedPratham.d.ts.map +1 -1
  51. package/convex/seedPratham.js +2 -3
  52. package/convex/seedPratham.js.map +1 -1
  53. package/convex/seedPratham.ts +2 -3
  54. package/convex/spendAlerts.js +2 -2
  55. package/convex/spendAlerts.js.map +1 -1
  56. package/convex/spendAlerts.ts +2 -2
  57. package/convex/stripeActions.d.ts.map +1 -1
  58. package/convex/stripeActions.js +25 -4
  59. package/convex/stripeActions.js.map +1 -1
  60. package/convex/stripeActions.ts +26 -4
  61. package/convex/updateAPIStatus.d.ts.map +1 -1
  62. package/convex/updateAPIStatus.js +0 -1
  63. package/convex/updateAPIStatus.js.map +1 -1
  64. package/convex/updateAPIStatus.ts +2 -3
  65. package/convex/workspaceSettings.d.ts +7 -0
  66. package/convex/workspaceSettings.d.ts.map +1 -0
  67. package/convex/workspaceSettings.js +128 -0
  68. package/convex/workspaceSettings.js.map +1 -0
  69. package/convex/workspaceSettings.ts +136 -0
  70. package/convex/workspaces.d.ts.map +1 -1
  71. package/convex/workspaces.js +15 -17
  72. package/convex/workspaces.js.map +1 -1
  73. package/convex/workspaces.ts +18 -20
  74. package/dist/bin.js +0 -0
  75. package/dist/cli/commands/demo.js +3 -3
  76. package/dist/cli/commands/demo.js.map +1 -1
  77. package/dist/cli/commands/doctor.js +1 -1
  78. package/dist/cli/commands/doctor.js.map +1 -1
  79. package/dist/cli/commands/login.js +1 -1
  80. package/dist/cli/commands/login.js.map +1 -1
  81. package/dist/cli/commands/setup.js +2 -2
  82. package/dist/cli/commands/setup.js.map +1 -1
  83. package/dist/credentials.d.ts.map +1 -1
  84. package/dist/credentials.js +15 -0
  85. package/dist/credentials.js.map +1 -1
  86. package/dist/discovery.js +1 -1
  87. package/dist/discovery.js.map +1 -1
  88. package/dist/execute.js +3 -3
  89. package/dist/execute.js.map +1 -1
  90. package/dist/index.js +14 -14
  91. package/dist/index.js.map +1 -1
  92. package/dist/open-apis.d.ts.map +1 -1
  93. package/dist/open-apis.js +94 -2
  94. package/dist/open-apis.js.map +1 -1
  95. package/dist/ui/errors.js +16 -16
  96. package/dist/ui/errors.js.map +1 -1
  97. package/dist/ui/prompts.js +1 -1
  98. package/dist/ui/prompts.js.map +1 -1
  99. package/email-templates/filestack-provider-outreach.html +1 -1
  100. package/email-templates/partnership-template.html +1 -1
  101. package/email-templates/pratham-partnership-draft.html +2 -2
  102. package/package.json +2 -2
  103. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  104. package/reports/session-report-2026-04-05.html +433 -0
  105. package/src/cli/commands/demo.ts +3 -3
  106. package/src/cli/commands/doctor.ts +1 -1
  107. package/src/cli/commands/login.ts +1 -1
  108. package/src/cli/commands/setup.ts +2 -2
  109. package/src/credentials.ts +16 -0
  110. package/src/discovery.ts +1 -1
  111. package/src/execute.ts +3 -3
  112. package/src/index.ts +15 -15
  113. package/src/open-apis.ts +114 -4
  114. package/src/ui/errors.ts +16 -16
  115. package/src/ui/prompts.ts +1 -1
@@ -62,14 +62,41 @@ export const registerProvider = mutation({
62
62
  discoveryCount: 0,
63
63
  });
64
64
 
65
- // Create session for auto-login after registration
66
- const sessionToken = generateToken();
67
- const sessionExpiresAt = now + 30 * 24 * 60 * 60 * 1000; // 30 days
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();
68
71
 
69
- await ctx.db.insert("sessions", {
70
- providerId,
71
- token: sessionToken,
72
- expiresAt: sessionExpiresAt,
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,
73
100
  createdAt: now,
74
101
  });
75
102
 
@@ -169,22 +196,21 @@ export const logDiscovery = mutation({
169
196
  callerWorkspaceId: v.string(),
170
197
  },
171
198
  handler: async (ctx, args) => {
172
- const providerEmailMap: Record<string, string> = {
173
- apilayer: "pratham.kumar@apilayer.com",
174
- filestack: "marketing@filestack.com",
175
- };
176
- const email = providerEmailMap[args.provider];
177
- if (!email) return { logged: false };
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 };
178
206
 
179
- const workspace = await ctx.db
180
- .query("workspaces")
181
- .withIndex("by_email", (q) => q.eq("email", email))
182
- .first();
207
+ const workspace = await ctx.db.get((providerRecord as any).workspaceId);
183
208
  if (!workspace) return { logged: false };
209
+ const wsId = workspace._id as any;
184
210
 
185
211
  // 1. Log to apiLogs (source of truth for Analytics)
186
212
  await ctx.db.insert("apiLogs", {
187
- workspaceId: workspace._id,
213
+ workspaceId: wsId,
188
214
  sessionToken: "",
189
215
  provider: args.provider,
190
216
  action: `discovery:${args.query}`,
@@ -196,31 +222,25 @@ export const logDiscovery = mutation({
196
222
  });
197
223
 
198
224
  // 2. Increment discoveryCount on MATCHING APIs only
199
- const providerRecord = await ctx.db
200
- .query("providers")
201
- .withIndex("by_email", (q) => q.eq("email", email))
202
- .first();
203
- if (providerRecord) {
204
- const apis = await ctx.db.query("providerAPIs").collect();
205
- const providerApis = apis.filter((a) => a.providerId === providerRecord._id);
206
- const queryLower = args.query.toLowerCase();
207
- const queryWords = queryLower.split(/\s+/).filter((w: string) => w.length > 2);
208
-
209
- let matched = 0;
210
- for (const api of providerApis) {
211
- const apiText = `${api.name} ${api.description || ""}`.toLowerCase();
212
- if (queryWords.some((w: string) => apiText.includes(w))) {
213
- await ctx.db.patch(api._id, {
214
- discoveryCount: ((api as any).discoveryCount || 0) + 1,
215
- lastDiscoveredAt: Date.now(),
216
- });
217
- matched++;
218
- }
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++;
219
241
  }
220
- return { logged: true, matched };
221
242
  }
222
-
223
- return { logged: true, matched: 0 };
243
+ return { logged: true, matched };
224
244
  },
225
245
  });
226
246
 
@@ -228,24 +248,19 @@ export const logDiscovery = mutation({
228
248
  export const trackDiscoveryByProvider = mutation({
229
249
  args: { provider: v.string(), query: v.string() },
230
250
  handler: async (ctx, args) => {
231
- // Find provider by email mapping
232
- const providerEmailMap: Record<string, string> = {
233
- apilayer: "pratham.kumar@apilayer.com",
234
- filestack: "marketing@filestack.com",
235
- };
236
- const email = providerEmailMap[args.provider];
237
- if (!email) return { updated: 0, error: "no email mapping" };
238
-
239
- // Find provider record by email
240
- const provider = await ctx.db
241
- .query("providers")
242
- .withIndex("by_email", (q) => q.eq("email", email))
243
- .first();
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
+ );
244
257
  if (!provider) return { updated: 0, error: "provider not found" };
245
258
 
246
259
  // Get all APIs for this provider
247
- const apis = await ctx.db.query("providerAPIs").collect();
248
- const providerApis = apis.filter((a) => a.providerId === provider._id);
260
+ const providerApis = await ctx.db
261
+ .query("providerAPIs")
262
+ .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
263
+ .collect();
249
264
 
250
265
  // Increment discoveryCount on ALL provider APIs
251
266
  for (const api of providerApis) {
@@ -314,14 +329,14 @@ export const getProviderStats = query({
314
329
  // DASHBOARD AUTH & SESSION FUNCTIONS
315
330
  // ============================================
316
331
 
317
- // Create magic link for email auth
332
+ // Create magic link for email auth (unified: writes to workspaceMagicLinks)
318
333
  export const createMagicLink = mutation({
319
334
  args: { email: v.string() },
320
335
  handler: async (ctx, { email }) => {
321
336
  const token = generateToken();
322
337
  const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes
323
338
 
324
- await ctx.db.insert("magicLinks", {
339
+ await ctx.db.insert("workspaceMagicLinks", {
325
340
  email: email.toLowerCase(),
326
341
  token,
327
342
  expiresAt,
@@ -332,12 +347,12 @@ export const createMagicLink = mutation({
332
347
  },
333
348
  });
334
349
 
335
- // Verify magic link and create session
350
+ // Verify magic link and create unified session (workspace + provider)
336
351
  export const verifyMagicLink = mutation({
337
352
  args: { token: v.string() },
338
353
  handler: async (ctx, { token }) => {
339
354
  const magicLink = await ctx.db
340
- .query("magicLinks")
355
+ .query("workspaceMagicLinks")
341
356
  .withIndex("by_token", (q) => q.eq("token", token))
342
357
  .first();
343
358
 
@@ -356,6 +371,30 @@ export const verifyMagicLink = mutation({
356
371
  // Mark as used
357
372
  await ctx.db.patch(magicLink._id, { usedAt: Date.now() });
358
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
+
359
398
  // Find or create provider
360
399
  let provider = await ctx.db
361
400
  .query("providers")
@@ -367,21 +406,23 @@ export const verifyMagicLink = mutation({
367
406
  email: magicLink.email,
368
407
  name: magicLink.email.split("@")[0],
369
408
  status: "approved",
370
- createdAt: Date.now(),
371
- updatedAt: Date.now(),
372
- });
409
+ workspaceId: workspace!._id,
410
+ createdAt: now,
411
+ updatedAt: now,
412
+ } as any);
373
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);
374
417
  }
375
418
 
376
- // Create session
419
+ // Create unified session (agentSessions)
377
420
  const sessionToken = generateToken();
378
- const sessionExpiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000; // 30 days
379
-
380
- await ctx.db.insert("sessions", {
381
- providerId: provider!._id,
382
- token: sessionToken,
383
- expiresAt: sessionExpiresAt,
384
- createdAt: Date.now(),
421
+ await ctx.db.insert("agentSessions", {
422
+ workspaceId: workspace!._id,
423
+ sessionToken,
424
+ lastUsedAt: now,
425
+ createdAt: now,
385
426
  });
386
427
 
387
428
  return {
@@ -396,10 +437,34 @@ export const verifyMagicLink = mutation({
396
437
  },
397
438
  });
398
439
 
399
- // Get current session
440
+ // Get current session (unified: tries agentSessions first, falls back to legacy sessions)
400
441
  export const getSession = query({
401
442
  args: { token: v.string() },
402
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)
403
468
  const session = await ctx.db
404
469
  .query("sessions")
405
470
  .withIndex("by_token", (q) => q.eq("token", token))
@@ -499,19 +564,35 @@ export const addAPI = mutation({
499
564
  }),
500
565
  },
501
566
  handler: async (ctx, args) => {
502
- // Verify session
503
- const session = await ctx.db
504
- .query("sessions")
505
- .withIndex("by_token", (q) => q.eq("token", args.token))
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))
506
573
  .first();
507
574
 
508
- if (!session || session.expiresAt < Date.now()) {
509
- throw new Error("Invalid or expired session");
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
+ }
510
589
  }
511
590
 
591
+ if (!providerId) throw new Error("Invalid or expired session");
592
+
512
593
  const now = Date.now();
513
594
  const apiId = await ctx.db.insert("providerAPIs", {
514
- providerId: session.providerId,
595
+ providerId,
515
596
  name: args.api.name,
516
597
  description: args.api.description,
517
598
  category: args.api.category,
@@ -536,19 +617,35 @@ export const deleteAPI = mutation({
536
617
  apiId: v.string(),
537
618
  },
538
619
  handler: async (ctx, args) => {
539
- // Verify session
540
- const session = await ctx.db
541
- .query("sessions")
542
- .withIndex("by_token", (q) => q.eq("token", args.token))
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))
543
626
  .first();
544
627
 
545
- if (!session || session.expiresAt < Date.now()) {
546
- throw new Error("Invalid or expired session");
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
+ }
547
642
  }
548
643
 
644
+ if (!providerId) throw new Error("Invalid or expired session");
645
+
549
646
  // Get the API and verify ownership
550
647
  const api = await ctx.db.get(args.apiId as any);
551
- if (!api || (api as any).providerId !== session.providerId) {
648
+ if (!api || (api as any).providerId !== providerId) {
552
649
  throw new Error("API not found or unauthorized");
553
650
  }
554
651
 
@@ -659,19 +756,51 @@ export const debugListProviders = query({
659
756
 
660
757
  export const getAnalytics = query({
661
758
  args: {
662
- token: v.string(),
759
+ token: v.optional(v.string()),
760
+ workspaceId: v.optional(v.string()), // Direct workspace ID (used by /workspace page)
663
761
  period: v.optional(v.string()), // "week", "month", "all"
664
762
  },
665
- handler: async (ctx, { token, period = "month" }) => {
666
- const session = await ctx.db
667
- .query("sessions")
668
- .withIndex("by_token", (q) => q.eq("token", token))
669
- .first();
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
+ }
670
774
 
671
- if (!session || session.expiresAt < Date.now()) {
672
- return null;
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
+ }
673
797
  }
674
798
 
799
+ if (!providerId) return null;
800
+
801
+ const provider = await ctx.db.get(providerId) as any;
802
+ if (!provider) return null;
803
+
675
804
  const now = Date.now();
676
805
  const periodMs = {
677
806
  week: 7 * 24 * 60 * 60 * 1000,
@@ -681,139 +810,101 @@ export const getAnalytics = query({
681
810
 
682
811
  const startTime = now - periodMs;
683
812
 
684
- // Get usage logs for this provider (from Direct Call usageLog)
685
- const usageLogs = await ctx.db
686
- .query("usageLog")
687
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
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))
688
820
  .collect();
689
821
 
690
- const periodCalls = usageLogs.filter((c) => c.timestamp >= startTime);
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:"));
691
827
 
692
828
  // Calculate metrics
693
- const totalCalls = periodCalls.length;
694
- const uniqueAgents = new Set(periodCalls.map((c) => c.userId)).size;
695
- const totalRevenue = periodCalls.reduce((sum, c) => sum + (c.creditsUsed / 100), 0); // cents to dollars
696
- const successCount = periodCalls.filter((c) => c.success).length;
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;
697
833
  const successRate = totalCalls > 0 ? (successCount / totalCalls) * 100 : 100;
698
- const avgLatency = totalCalls > 0
699
- ? periodCalls.reduce((sum, c) => sum + c.latencyMs, 0) / totalCalls
834
+ const avgLatency = totalCalls > 0
835
+ ? Math.round(directCalls.reduce((sum, l) => sum + l.latencyMs, 0) / totalCalls)
700
836
  : 0;
701
837
 
702
838
  // Calls over time (daily buckets)
703
- const callsByDay: Record<string, { calls: number; revenue: number; success: number }> = {};
839
+ const callsByDay: Record<string, { calls: number; discoveries: number; success: number }> = {};
704
840
 
705
- periodCalls.forEach((call) => {
706
- const day = new Date(call.timestamp).toISOString().split("T")[0];
841
+ periodLogs.forEach((log) => {
842
+ const day = new Date(log.createdAt).toISOString().split("T")[0];
707
843
  if (!callsByDay[day]) {
708
- callsByDay[day] = { calls: 0, revenue: 0, success: 0 };
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;
709
851
  }
710
- callsByDay[day].calls += 1;
711
- callsByDay[day].revenue += call.creditsUsed / 100;
712
- if (call.success) callsByDay[day].success += 1;
713
852
  });
714
853
 
715
- // Top agents (users)
716
- const agentCallCounts: Record<string, number> = {};
717
- periodCalls.forEach((call) => {
718
- agentCallCounts[call.userId] = (agentCallCounts[call.userId] || 0) + 1;
719
- });
720
- const topAgents = Object.entries(agentCallCounts)
721
- .sort((a, b) => b[1] - a[1])
722
- .slice(0, 10)
723
- .map(([agentId, calls]) => ({ agentId, calls }));
724
-
725
854
  // Top actions
726
855
  const actionCallCounts: Record<string, number> = {};
727
- periodCalls.forEach((call) => {
728
- actionCallCounts[call.actionName] = (actionCallCounts[call.actionName] || 0) + 1;
856
+ directCalls.forEach((log) => {
857
+ const action = (log as any).action || "unknown";
858
+ actionCallCounts[action] = (actionCallCounts[action] || 0) + 1;
729
859
  });
730
860
  const topActions = Object.entries(actionCallCounts)
731
861
  .sort((a, b) => b[1] - a[1])
732
862
  .slice(0, 10)
733
863
  .map(([actionName, calls]) => ({ actionName, calls }));
734
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
+
735
876
  // Get provider's APIs
736
877
  const apis = await ctx.db
737
878
  .query("providerAPIs")
738
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
739
- .collect();
740
-
741
- // Get Direct Call configs to map directCallId to apiId
742
- const directCallConfigs = await ctx.db
743
- .query("providerDirectCall")
744
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
879
+ .withIndex("by_providerId", (q) => q.eq("providerId", providerId))
745
880
  .collect();
746
881
 
747
- // Calls per API (via directCallId apiId mapping)
748
- const callsByDirectCallId: Record<string, number> = {};
749
- periodCalls.forEach((call) => {
750
- const dcId = call.directCallId as string;
751
- callsByDirectCallId[dcId] = (callsByDirectCallId[dcId] || 0) + 1;
752
- });
753
-
754
- // Map to apiId
755
- const callsByApiId: Record<string, number> = {};
756
- directCallConfigs.forEach((dc) => {
757
- if (dc.apiId) {
758
- callsByApiId[dc.apiId as string] = callsByDirectCallId[dc._id as string] || 0;
759
- }
760
- });
761
-
762
- // Preview data for providers with no usage yet
763
- const isPreview = totalCalls === 0;
764
-
765
- if (isPreview) {
766
- // Generate preview data so providers can see what the dashboard looks like
767
- const previewDays = [];
768
- for (let i = 13; i >= 0; i--) {
769
- const date = new Date(now - i * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
770
- previewDays.push({
771
- date,
772
- calls: Math.floor(Math.random() * 50) + 10,
773
- revenue: Math.random() * 5,
774
- });
775
- }
776
-
777
- return {
778
- totalCalls: 847,
779
- uniqueAgents: 23,
780
- totalRevenue: 42.35,
781
- successRate: 98.2,
782
- avgLatency: 145,
783
- callsByDay: previewDays,
784
- topAgents: [
785
- { agentId: "agent_demo_1", calls: 234 },
786
- { agentId: "agent_demo_2", calls: 189 },
787
- { agentId: "agent_demo_3", calls: 156 },
788
- { agentId: "agent_demo_4", calls: 98 },
789
- { agentId: "agent_demo_5", calls: 67 },
790
- ],
791
- topActions: [
792
- { actionName: "send_message", calls: 412 },
793
- { actionName: "get_status", calls: 289 },
794
- { actionName: "create_invoice", calls: 146 },
795
- ],
796
- apis: apis.map((api) => ({
797
- id: api._id,
798
- name: api.name,
799
- calls: Math.floor(Math.random() * 200) + 50,
800
- status: api.status,
801
- })),
802
- isPreview: true,
803
- };
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;
804
893
  }
805
894
 
806
895
  return {
807
896
  totalCalls,
808
- uniqueAgents,
809
- totalRevenue,
810
- successRate,
897
+ totalDiscoveries,
898
+ uniqueAgents: uniqueCallers,
899
+ totalRevenue: 0, // Revenue tracking not yet implemented
900
+ successRate: Math.round(successRate * 10) / 10,
811
901
  avgLatency,
812
902
  callsByDay: Object.entries(callsByDay)
813
903
  .map(([date, data]) => ({
814
904
  date,
815
905
  calls: data.calls,
816
- revenue: data.revenue,
906
+ discoveries: data.discoveries,
907
+ revenue: 0,
817
908
  }))
818
909
  .sort((a, b) => a.date.localeCompare(b.date)),
819
910
  topAgents,
@@ -821,7 +912,8 @@ export const getAnalytics = query({
821
912
  apis: apis.map((api) => ({
822
913
  id: api._id,
823
914
  name: api.name,
824
- calls: callsByApiId[api._id as string] || 0,
915
+ calls: apiCallCounts[api._id as string] || 0,
916
+ discoveries: apiDiscoveryCounts[api._id as string] || 0,
825
917
  status: api.status,
826
918
  })),
827
919
  isPreview: false,
@@ -833,28 +925,46 @@ export const getAnalytics = query({
833
925
  // DASHBOARD EARNINGS
834
926
  // ============================================
835
927
 
928
+ // Earnings placeholder - partners (APILayer, Filestack) don't earn per-call revenue yet
836
929
  export const getEarnings = query({
837
930
  args: { token: v.string() },
838
931
  handler: async (ctx, { token }) => {
839
- const session = await ctx.db
840
- .query("sessions")
841
- .withIndex("by_token", (q) => q.eq("token", 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))
842
938
  .first();
843
939
 
844
- if (!session || session.expiresAt < Date.now()) {
845
- return null;
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
+ }
846
954
  }
847
955
 
848
- // Get all payouts
956
+ if (!providerId) return null;
957
+
958
+ // Get all payouts (currently empty for all providers)
849
959
  const payouts = await ctx.db
850
960
  .query("payouts")
851
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
961
+ .withIndex("by_providerId", (q) => q.eq("providerId", providerId))
852
962
  .collect();
853
963
 
854
- // Get all API calls to calculate pending
964
+ // Get all API calls (legacy table, currently empty)
855
965
  const allCalls = await ctx.db
856
966
  .query("apiCalls")
857
- .withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
967
+ .withIndex("by_providerId", (q) => q.eq("providerId", providerId))
858
968
  .collect();
859
969
 
860
970
  // Find last completed payout
@@ -872,7 +982,7 @@ export const getEarnings = query({
872
982
  const totalEarned = allCalls.reduce((sum, c) => sum + c.costUsd, 0);
873
983
 
874
984
  // Get provider for Stripe status
875
- const provider = await ctx.db.get(session.providerId);
985
+ const provider = await ctx.db.get(providerId);
876
986
 
877
987
  return {
878
988
  pendingAmount,