@nordsym/apiclaw 1.7.2 → 1.7.4

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