@nordsym/apiclaw 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +15 -2
  2. package/dist/bin-http.js +0 -0
  3. package/dist/bin.bundled.js +79288 -0
  4. package/dist/gateway-client.d.ts.map +1 -1
  5. package/dist/gateway-client.js +24 -2
  6. package/dist/gateway-client.js.map +1 -1
  7. package/dist/index.bundled.js +61263 -0
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -2
  11. package/.claude/settings.local.json +0 -13
  12. package/.env.prod +0 -1
  13. package/apiclaw-README.md +0 -494
  14. package/convex/_generated/api.d.ts +0 -145
  15. package/convex/_generated/api.js +0 -23
  16. package/convex/_generated/dataModel.d.ts +0 -60
  17. package/convex/_generated/server.d.ts +0 -143
  18. package/convex/_generated/server.js +0 -93
  19. package/convex/_listWorkspaces.ts +0 -13
  20. package/convex/adminActivate.ts +0 -53
  21. package/convex/adminStats.ts +0 -306
  22. package/convex/agents.ts +0 -939
  23. package/convex/analytics.ts +0 -187
  24. package/convex/apiKeys.ts +0 -220
  25. package/convex/backfillAnalytics.ts +0 -272
  26. package/convex/backfillSearchLogs.ts +0 -35
  27. package/convex/billing.ts +0 -834
  28. package/convex/capabilities.ts +0 -157
  29. package/convex/chains.ts +0 -1318
  30. package/convex/credits.ts +0 -211
  31. package/convex/crons.ts +0 -65
  32. package/convex/debugFilestackLogs.ts +0 -16
  33. package/convex/debugGetToken.ts +0 -18
  34. package/convex/directCall.ts +0 -713
  35. package/convex/earnProgress.ts +0 -753
  36. package/convex/email.ts +0 -329
  37. package/convex/feedback.ts +0 -265
  38. package/convex/funnel.ts +0 -431
  39. package/convex/guards.ts +0 -174
  40. package/convex/http.ts +0 -3756
  41. package/convex/inbound.ts +0 -32
  42. package/convex/logs.ts +0 -701
  43. package/convex/migrateFilestack.ts +0 -81
  44. package/convex/migratePartnersProd.ts +0 -174
  45. package/convex/migratePratham.ts +0 -126
  46. package/convex/migrateProviderWorkspaces.ts +0 -175
  47. package/convex/mou.ts +0 -91
  48. package/convex/nurture.ts +0 -355
  49. package/convex/providerKeys.ts +0 -289
  50. package/convex/providers.ts +0 -1135
  51. package/convex/purchases.ts +0 -183
  52. package/convex/ratelimit.ts +0 -104
  53. package/convex/schema.ts +0 -926
  54. package/convex/searchLogs.ts +0 -265
  55. package/convex/seedAPILayerAPIs.ts +0 -191
  56. package/convex/seedDirectCallConfigs.ts +0 -336
  57. package/convex/seedPratham.ts +0 -149
  58. package/convex/spendAlerts.ts +0 -442
  59. package/convex/stripeActions.ts +0 -607
  60. package/convex/teams.ts +0 -243
  61. package/convex/telemetry.ts +0 -81
  62. package/convex/tsconfig.json +0 -25
  63. package/convex/updateAPIStatus.ts +0 -44
  64. package/convex/usage.ts +0 -260
  65. package/convex/usageReports.ts +0 -357
  66. package/convex/waitlist.ts +0 -55
  67. package/convex/webhooks.ts +0 -494
  68. package/convex/workspaceSettings.ts +0 -143
  69. package/convex/workspaces.ts +0 -1331
  70. package/convex.json +0 -3
  71. package/direct-test.mjs +0 -51
  72. package/email-templates/filestack-provider-outreach.html +0 -162
  73. package/email-templates/partnership-template.html +0 -116
  74. package/email-templates/pratham-draft-preview.txt +0 -57
  75. package/email-templates/pratham-partnership-draft.html +0 -141
  76. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  77. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  78. package/reports/pipeline/acquire_apisguru.json +0 -17
  79. package/reports/pipeline/capabilities.json +0 -38
  80. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  81. package/reports/pipeline/discover_github.json +0 -25
  82. package/reports/pipeline/discover_github_repos.json +0 -49
  83. package/reports/pipeline/discover_swaggerhub.json +0 -24
  84. package/reports/pipeline/discover_well_known.json +0 -23
  85. package/reports/pipeline/fetch_specs.json +0 -19
  86. package/reports/pipeline/generate_providers.json +0 -14
  87. package/reports/pipeline/match_registry.json +0 -11
  88. package/reports/pipeline/parse_specs.json +0 -17
  89. package/reports/pipeline/promote_candidates.json +0 -34
  90. package/reports/pipeline/validate.json +0 -30
  91. package/reports/pipeline/validate_smoke_details.json +0 -3835
  92. package/reports/session-report-2026-04-05.html +0 -433
  93. package/seed-apis-direct.mjs +0 -106
  94. package/src/access-control.ts +0 -174
  95. package/src/adapters/base.ts +0 -364
  96. package/src/adapters/claude-desktop.ts +0 -41
  97. package/src/adapters/cline.ts +0 -88
  98. package/src/adapters/continue.ts +0 -91
  99. package/src/adapters/cursor.ts +0 -43
  100. package/src/adapters/custom.ts +0 -188
  101. package/src/adapters/detect.ts +0 -202
  102. package/src/adapters/index.ts +0 -47
  103. package/src/adapters/windsurf.ts +0 -44
  104. package/src/bin-http.ts +0 -45
  105. package/src/bin.ts +0 -34
  106. package/src/capability-router.ts +0 -331
  107. package/src/chainExecutor.ts +0 -730
  108. package/src/chainResolver.test.ts +0 -246
  109. package/src/chainResolver.ts +0 -658
  110. package/src/cli/commands/demo.ts +0 -109
  111. package/src/cli/commands/doctor.ts +0 -435
  112. package/src/cli/commands/index.ts +0 -9
  113. package/src/cli/commands/login.ts +0 -203
  114. package/src/cli/commands/mcp-install.ts +0 -373
  115. package/src/cli/commands/restore.ts +0 -333
  116. package/src/cli/commands/setup.ts +0 -297
  117. package/src/cli/commands/uninstall.ts +0 -240
  118. package/src/cli/index.ts +0 -148
  119. package/src/cli.ts +0 -370
  120. package/src/confirmation.ts +0 -296
  121. package/src/credentials.ts +0 -455
  122. package/src/credits.ts +0 -329
  123. package/src/crypto.ts +0 -75
  124. package/src/discovery.ts +0 -568
  125. package/src/enterprise/env.ts +0 -156
  126. package/src/enterprise/index.ts +0 -7
  127. package/src/enterprise/script-generator.ts +0 -481
  128. package/src/execute-dynamic.ts +0 -617
  129. package/src/execute.ts +0 -2386
  130. package/src/funnel-client.ts +0 -168
  131. package/src/funnel.test.ts +0 -187
  132. package/src/gateway-client.ts +0 -192
  133. package/src/hivr-whitelist.ts +0 -110
  134. package/src/http-api.ts +0 -286
  135. package/src/http-server-minimal.ts +0 -154
  136. package/src/index.ts +0 -2702
  137. package/src/intelligent-gateway.ts +0 -339
  138. package/src/mcp-analytics.ts +0 -156
  139. package/src/metered.ts +0 -149
  140. package/src/open-apis-generated.ts +0 -157
  141. package/src/open-apis.ts +0 -558
  142. package/src/postinstall.ts +0 -40
  143. package/src/product-whitelist.ts +0 -246
  144. package/src/proxy.ts +0 -36
  145. package/src/registration-guard.ts +0 -117
  146. package/src/session.ts +0 -129
  147. package/src/stripe.ts +0 -497
  148. package/src/telemetry.ts +0 -71
  149. package/src/test.ts +0 -135
  150. package/src/types/convex-api.d.ts +0 -20
  151. package/src/types/convex-api.ts +0 -21
  152. package/src/types.ts +0 -109
  153. package/src/ui/colors.ts +0 -219
  154. package/src/ui/errors.ts +0 -394
  155. package/src/ui/index.ts +0 -17
  156. package/src/ui/prompts.ts +0 -390
  157. package/src/ui/spinner.ts +0 -325
  158. package/src/utils/backup.ts +0 -224
  159. package/src/utils/config.ts +0 -318
  160. package/src/utils/os.ts +0 -124
  161. package/src/utils/paths.ts +0 -203
  162. package/src/webhook.ts +0 -107
  163. package/test-10-working.cjs +0 -97
  164. package/test-14-final.cjs +0 -96
  165. package/test-actual-handlers.ts +0 -92
  166. package/test-apilayer-all-14.ts +0 -249
  167. package/test-apilayer-fixed.ts +0 -248
  168. package/test-direct-endpoints.ts +0 -174
  169. package/test-exact-endpoints.ts +0 -144
  170. package/test-final.ts +0 -83
  171. package/test-full-routing.ts +0 -100
  172. package/test-handlers-correct.ts +0 -217
  173. package/test-numverify-key.ts +0 -41
  174. package/test-via-handlers.ts +0 -92
  175. package/test-worldnews.mjs +0 -26
  176. package/tsconfig.json +0 -20
package/convex/schema.ts DELETED
@@ -1,926 +0,0 @@
1
- import { defineSchema, defineTable } from "convex/server";
2
- import { v } from "convex/values";
3
-
4
- export default defineSchema({
5
- // Credits per agent
6
- agentCredits: defineTable({
7
- agentId: v.string(),
8
- balanceUsd: v.number(),
9
- currency: v.string(),
10
- createdAt: v.number(),
11
- updatedAt: v.number(),
12
- }).index("by_agentId", ["agentId"]),
13
-
14
- // Purchases (API access bought by agents)
15
- purchases: defineTable({
16
- agentId: v.string(),
17
- providerId: v.string(),
18
- amountUsd: v.number(),
19
- creditsGranted: v.number(),
20
- status: v.string(), // active, exhausted, refunded
21
- credentials: v.optional(v.any()),
22
- createdAt: v.number(),
23
- })
24
- .index("by_agentId", ["agentId"])
25
- .index("by_providerId", ["providerId"])
26
- .index("by_agentId_providerId", ["agentId", "providerId"]),
27
-
28
- // Usage tracking per purchase
29
- usage: defineTable({
30
- purchaseId: v.id("purchases"),
31
- providerId: v.string(),
32
- unitsUsed: v.number(),
33
- unitsRemaining: v.number(),
34
- costIncurredUsd: v.number(),
35
- lastUsedAt: v.number(),
36
- })
37
- .index("by_purchaseId", ["purchaseId"])
38
- .index("by_providerId", ["providerId"]),
39
-
40
- // ============================================
41
- // WORKSPACE TABLES (MCP Agent Authentication)
42
- // ============================================
43
-
44
- // Workspaces (agent owner accounts)
45
- workspaces: defineTable({
46
- email: v.string(),
47
- workspaceName: v.optional(v.string()), // Display name (e.g., "APILayer", "My Team")
48
- passwordHash: v.optional(v.string()),
49
- status: v.string(), // "pending" | "active" | "suspended"
50
- tier: v.string(), // "free" | "pro" | "scale" | "usage_based" | "enterprise" | "partner"
51
- usageCount: v.number(), // total API calls made (lifetime)
52
- usageLimit: v.number(), // max API calls for tier
53
- // Weekly usage (resets every Monday 00:00 UTC)
54
- weeklyUsageCount: v.optional(v.number()), // calls this week
55
- weeklyUsageLimit: v.optional(v.number()), // 50 for free, unlimited for paid tiers
56
- lastWeeklyResetAt: v.optional(v.number()), // timestamp of last reset
57
- // Hourly rate limit
58
- hourlyUsageCount: v.optional(v.number()), // calls this hour
59
- lastHourlyResetAt: v.optional(v.number()), // timestamp of last hourly reset
60
- // Legacy field (no longer used)
61
- backerUntil: v.optional(v.number()),
62
- // Main agent identification
63
- mainAgentId: v.optional(v.string()), // UUID, auto-generated on first call
64
- mainAgentName: v.optional(v.string()), // Auto-generated name (e.g., "Crimson Phoenix")
65
- // AI Backend tracking
66
- aiBackend: v.optional(v.string()), // "claude-3-opus", "gpt-4", etc.
67
- aiBackendLastSeen: v.optional(v.number()), // timestamp of last AI backend header
68
- // Stripe billing fields
69
- stripeCustomerId: v.optional(v.string()),
70
- stripeSubscriptionId: v.optional(v.string()),
71
- billingPlan: v.optional(v.string()), // "free" | "usage_based" | "starter" | "pro" | "scale"
72
- creditBalance: v.optional(v.number()), // prepaid credits in cents
73
- lastBillingDate: v.optional(v.number()),
74
- // Payment method fields
75
- hasPaymentMethod: v.optional(v.boolean()),
76
- paymentMethodType: v.optional(v.string()),
77
- cardBrand: v.optional(v.string()),
78
- cardLast4: v.optional(v.string()),
79
- // Referral fields
80
- referralCode: v.optional(v.string()), // CLAW-XXXXXX format
81
- referredBy: v.optional(v.id("workspaces")), // who referred this user
82
- // Budget & Spend Alerts (PRD 2.6)
83
- budgetCap: v.optional(v.number()), // Monthly budget cap in USD cents (null = unlimited)
84
- budgetAlertSentAt: v.optional(v.number()), // When 80% alert was last sent (resets monthly)
85
- pauseOnBudgetExceeded: v.optional(v.boolean()), // If true, block execution when budget exceeded
86
- monthlySpendCents: v.optional(v.number()), // Current month's spend in cents
87
- lastSpendResetAt: v.optional(v.number()), // When monthly spend was last reset
88
- // Activity tracking
89
- lastActiveAt: v.optional(v.number()), // Last API call timestamp (main agent)
90
- createdAt: v.number(),
91
- updatedAt: v.number(),
92
- })
93
- .index("by_email", ["email"])
94
- .index("by_stripeCustomerId", ["stripeCustomerId"])
95
- .index("by_stripeSubscriptionId", ["stripeSubscriptionId"])
96
- .index("by_status", ["status"])
97
- .index("by_referralCode", ["referralCode"])
98
- .index("by_mainAgentId", ["mainAgentId"]),
99
-
100
- // Invoices (Stripe invoice records)
101
- invoices: defineTable({
102
- workspaceId: v.id("workspaces"),
103
- stripeInvoiceId: v.string(),
104
- amount: v.number(), // in cents
105
- status: v.string(), // "paid" | "pending" | "failed" | "void"
106
- periodStart: v.number(),
107
- periodEnd: v.number(),
108
- callCount: v.number(),
109
- pdfUrl: v.optional(v.string()),
110
- createdAt: v.number(),
111
- })
112
- .index("by_workspaceId", ["workspaceId"])
113
- .index("by_stripeInvoiceId", ["stripeInvoiceId"])
114
- .index("by_workspaceId_createdAt", ["workspaceId", "createdAt"]),
115
-
116
- // Usage records (daily aggregation for billing)
117
- usageRecords: defineTable({
118
- workspaceId: v.id("workspaces"),
119
- date: v.string(), // "2026-02-28" format
120
- callCount: v.number(),
121
- providerCostUsd: v.optional(v.float64()), // actual provider cost accumulated
122
- apiclawCostUsd: v.optional(v.float64()), // provider cost + 15% margin accumulated
123
- reportedToStripe: v.boolean(),
124
- stripeUsageRecordId: v.optional(v.string()),
125
- createdAt: v.number(),
126
- updatedAt: v.number(),
127
- })
128
- .index("by_workspaceId", ["workspaceId"])
129
- .index("by_date", ["date"])
130
- .index("by_workspaceId_date", ["workspaceId", "date"])
131
- .index("by_reportedToStripe", ["reportedToStripe"]),
132
-
133
- // Workspace API Keys (persistent keys for programmatic access)
134
- workspaceApiKeys: defineTable({
135
- workspaceId: v.id("workspaces"),
136
- key: v.string(), // "sk-claw-" + 48 random chars (hashed after creation)
137
- keyHash: v.string(), // SHA-256 hash for lookup (key itself not stored after first show)
138
- keyPrefix: v.string(), // "sk-claw-...last4" for display
139
- name: v.string(), // user label ("Production", "My Agent")
140
- lastUsedAt: v.optional(v.number()),
141
- createdAt: v.number(),
142
- revokedAt: v.optional(v.number()),
143
- })
144
- .index("by_keyHash", ["keyHash"])
145
- .index("by_workspaceId", ["workspaceId"]),
146
-
147
- // Agent sessions (for MCP server authentication)
148
- agentSessions: defineTable({
149
- workspaceId: v.id("workspaces"),
150
- sessionToken: v.string(),
151
- fingerprint: v.optional(v.string()), // machine fingerprint
152
- customName: v.optional(v.string()), // user-defined name
153
- lastUsedAt: v.number(),
154
- createdAt: v.number(),
155
- })
156
- .index("by_sessionToken", ["sessionToken"])
157
- .index("by_workspaceId", ["workspaceId"]),
158
-
159
- // Agents — one per unique (fingerprint, mcpClient) pair
160
- // An agent = an MCP client installation, NOT a login session
161
- agents: defineTable({
162
- fingerprint: v.string(), // hostname:username
163
- mcpClient: v.string(), // "claude-desktop" | "claude-code" | "cursor" | "windsurf" | "cline" | "continue" | "unknown"
164
- workspaceId: v.id("workspaces"), // always linked — auto-created on first call
165
- name: v.optional(v.string()), // auto-generated or user-set
166
- aiBackend: v.optional(v.string()), // "claude-3-opus" etc
167
- platform: v.optional(v.string()), // "darwin" | "linux" | "win32"
168
- callCount: v.number(),
169
- firstSeenAt: v.number(),
170
- lastActiveAt: v.number(),
171
- })
172
- .index("by_fingerprint_client", ["fingerprint", "mcpClient"])
173
- .index("by_workspaceId", ["workspaceId"])
174
- .index("by_lastActiveAt", ["lastActiveAt"])
175
- .index("by_mcpClient", ["mcpClient"]),
176
-
177
- // Subagent tracking (tasks spawned by main agent)
178
- subagents: defineTable({
179
- workspaceId: v.id("workspaces"),
180
- subagentId: v.string(), // from X-APIClaw-Subagent header
181
- name: v.optional(v.string()), // optional display name
182
- description: v.optional(v.string()), // user-provided description
183
- aiBackend: v.optional(v.string()), // "claude-3-opus", "gpt-4", etc.
184
- isRegistered: v.optional(v.boolean()), // true if pre-registered (not implicit)
185
- callCount: v.number(),
186
- firstSeenAt: v.number(),
187
- lastActiveAt: v.number(),
188
- })
189
- .index("by_workspaceId", ["workspaceId"])
190
- .index("by_workspaceId_subagentId", ["workspaceId", "subagentId"])
191
- .index("by_lastActiveAt", ["lastActiveAt"]),
192
-
193
- // Search logs (analytics for workspace searches)
194
- searchLogs: defineTable({
195
- workspaceId: v.id("workspaces"),
196
- subagentId: v.optional(v.string()),
197
- query: v.string(),
198
- resultCount: v.number(),
199
- hasResults: v.boolean(),
200
- matchedProviders: v.optional(v.array(v.string())),
201
- responseTimeMs: v.number(),
202
- timestamp: v.number(),
203
- })
204
- .index("by_workspaceId", ["workspaceId"])
205
- .index("by_timestamp", ["timestamp"])
206
- .index("by_hasResults", ["hasResults"])
207
- .index("by_workspaceId_timestamp", ["workspaceId", "timestamp"]),
208
-
209
- // Workspace team members (invite-based access)
210
- workspaceMembers: defineTable({
211
- workspaceId: v.id("workspaces"),
212
- email: v.string(),
213
- role: v.union(v.literal("owner"), v.literal("admin"), v.literal("member")),
214
- invitedBy: v.optional(v.string()), // email of inviter
215
- inviteToken: v.optional(v.string()),
216
- status: v.union(v.literal("pending"), v.literal("active"), v.literal("revoked")),
217
- createdAt: v.number(),
218
- acceptedAt: v.optional(v.number()),
219
- })
220
- .index("by_workspaceId", ["workspaceId"])
221
- .index("by_email", ["email"])
222
- .index("by_inviteToken", ["inviteToken"])
223
- .index("by_workspaceId_email", ["workspaceId", "email"]),
224
-
225
- // Magic links for workspace email verification
226
- workspaceMagicLinks: defineTable({
227
- email: v.string(),
228
- token: v.string(),
229
- sessionFingerprint: v.optional(v.string()),
230
- expiresAt: v.number(),
231
- usedAt: v.optional(v.number()),
232
- createdAt: v.number(),
233
- })
234
- .index("by_token", ["token"])
235
- .index("by_email", ["email"]),
236
-
237
- // Credit top-ups (from Stripe payments)
238
- creditTopups: defineTable({
239
- agentId: v.string(),
240
- stripePaymentIntentId: v.optional(v.string()),
241
- stripeSessionId: v.optional(v.string()),
242
- amountUsd: v.number(),
243
- creditsGranted: v.number(),
244
- packageType: v.string(), // starter, growth, scale
245
- status: v.string(), // pending, completed, failed
246
- createdAt: v.number(),
247
- completedAt: v.optional(v.number()),
248
- })
249
- .index("by_agentId", ["agentId"])
250
- .index("by_stripeSessionId", ["stripeSessionId"])
251
- .index("by_stripePaymentIntentId", ["stripePaymentIntentId"]),
252
-
253
- // ============================================
254
- // PROVIDER TABLES (for provider dashboard)
255
- // ============================================
256
-
257
- // API Providers (companies/individuals offering APIs)
258
- providers: defineTable({
259
- email: v.string(),
260
- name: v.string(),
261
- company: v.optional(v.string()),
262
- website: v.optional(v.string()),
263
- avatarUrl: v.optional(v.string()),
264
- stripeConnectId: v.optional(v.string()), // for payouts
265
- stripeOnboardingComplete: v.optional(v.boolean()),
266
- status: v.string(), // pending, approved, rejected, suspended
267
- workspaceId: v.optional(v.id("workspaces")), // Link to unified workspace identity
268
- createdAt: v.number(),
269
- updatedAt: v.number(),
270
- approvedAt: v.optional(v.number()),
271
- })
272
- .index("by_email", ["email"])
273
- .index("by_stripeConnectId", ["stripeConnectId"])
274
- .index("by_status", ["status"])
275
- .index("by_workspaceId", ["workspaceId"]),
276
-
277
- // APIs listed by providers (self-service onboarding)
278
- providerAPIs: defineTable({
279
- providerId: v.optional(v.id("providers")), // legacy — prefer workspaceId
280
- workspaceId: v.optional(v.id("workspaces")), // new — workspace owns this API
281
- name: v.string(),
282
- description: v.string(),
283
- category: v.string(),
284
- openApiUrl: v.optional(v.string()),
285
- docsUrl: v.optional(v.string()),
286
- pricingModel: v.string(), // free, freemium, paid
287
- pricingNotes: v.optional(v.string()),
288
- status: v.string(), // active, paused
289
- createdAt: v.number(),
290
- approvedAt: v.optional(v.number()),
291
- // Analytics
292
- discoveryCount: v.optional(v.number()),
293
- lastDiscoveredAt: v.optional(v.number()),
294
- })
295
- .index("by_providerId", ["providerId"])
296
- .index("by_workspaceId", ["workspaceId"])
297
- .index("by_category", ["category"])
298
- .index("by_status", ["status"])
299
- .index("by_status_category", ["status", "category"]),
300
-
301
- // APIs listed by providers (for full dashboard)
302
- apis: defineTable({
303
- providerId: v.id("providers"),
304
- workspaceId: v.optional(v.id("workspaces")), // Parallel workspace link
305
- name: v.string(),
306
- description: v.string(),
307
- category: v.string(),
308
- icon: v.optional(v.string()), // emoji or URL
309
- baseUrl: v.string(),
310
- docsUrl: v.optional(v.string()),
311
- authType: v.string(), // api_key, oauth, basic, bearer
312
- pricingModel: v.string(), // free, per_call, monthly, credits
313
- pricePerCall: v.optional(v.number()), // in USD cents
314
- monthlyPrice: v.optional(v.number()), // in USD cents
315
- rateLimitPerMinute: v.optional(v.number()),
316
- regions: v.optional(v.array(v.string())),
317
- tags: v.optional(v.array(v.string())),
318
- status: v.string(), // active, paused, pending_review
319
- isPublic: v.boolean(),
320
- // Credentials (encrypted in production)
321
- credentialTemplate: v.optional(v.any()),
322
- createdAt: v.number(),
323
- updatedAt: v.number(),
324
- })
325
- .index("by_providerId", ["providerId"])
326
- .index("by_workspaceId", ["workspaceId"])
327
- .index("by_category", ["category"])
328
- .index("by_status", ["status"]),
329
-
330
- // API Calls / Usage logs (for analytics)
331
- apiCalls: defineTable({
332
- apiId: v.id("apis"),
333
- providerId: v.id("providers"),
334
- workspaceId: v.optional(v.id("workspaces")), // Parallel workspace link
335
- agentId: v.string(),
336
- endpoint: v.optional(v.string()),
337
- method: v.optional(v.string()),
338
- statusCode: v.optional(v.number()),
339
- latencyMs: v.optional(v.number()),
340
- costUsd: v.number(), // cost in USD (fractional)
341
- region: v.optional(v.string()),
342
- timestamp: v.number(),
343
- })
344
- .index("by_apiId", ["apiId"])
345
- .index("by_providerId", ["providerId"])
346
- .index("by_workspaceId", ["workspaceId"])
347
- .index("by_agentId", ["agentId"])
348
- .index("by_timestamp", ["timestamp"])
349
- .index("by_providerId_timestamp", ["providerId", "timestamp"]),
350
-
351
- // Provider Payouts
352
- payouts: defineTable({
353
- providerId: v.id("providers"),
354
- workspaceId: v.optional(v.id("workspaces")), // Parallel workspace link
355
- amountUsd: v.number(),
356
- status: v.string(), // pending, processing, completed, failed
357
- stripePayoutId: v.optional(v.string()),
358
- periodStart: v.number(),
359
- periodEnd: v.number(),
360
- createdAt: v.number(),
361
- completedAt: v.optional(v.number()),
362
- })
363
- .index("by_providerId", ["providerId"])
364
- .index("by_workspaceId", ["workspaceId"])
365
- .index("by_status", ["status"]),
366
-
367
- // OTP codes for terminal-native email verification
368
- otpCodes: defineTable({
369
- email: v.string(),
370
- code: v.string(), // 6-digit code
371
- fingerprint: v.optional(v.string()),
372
- expiresAt: v.number(),
373
- usedAt: v.optional(v.number()),
374
- attempts: v.number(), // failed attempts counter
375
- createdAt: v.number(),
376
- })
377
- .index("by_email", ["email"])
378
- .index("by_email_code", ["email", "code"]),
379
-
380
- // Magic link tokens for email auth
381
- magicLinks: defineTable({
382
- email: v.string(),
383
- token: v.string(),
384
- expiresAt: v.number(),
385
- usedAt: v.optional(v.number()),
386
- createdAt: v.number(),
387
- })
388
- .index("by_token", ["token"])
389
- .index("by_email", ["email"]),
390
-
391
- // Sessions for authenticated providers
392
- sessions: defineTable({
393
- providerId: v.id("providers"),
394
- token: v.string(),
395
- expiresAt: v.number(),
396
- createdAt: v.number(),
397
- })
398
- .index("by_token", ["token"])
399
- .index("by_providerId", ["providerId"]),
400
-
401
- // Rate limiting
402
- rateLimits: defineTable({
403
- key: v.string(),
404
- identifier: v.string(),
405
- action: v.string(),
406
- count: v.number(),
407
- hourBucket: v.number(),
408
- createdAt: v.number(),
409
- })
410
- .index("by_key", ["key"])
411
- .index("by_identifier", ["identifier"]),
412
-
413
- // Usage analytics
414
- analytics: defineTable({
415
- event: v.string(), // "discovery", "instant", "search_query"
416
- provider: v.optional(v.string()),
417
- query: v.optional(v.string()),
418
- identifier: v.string(),
419
- workspaceId: v.optional(v.id("workspaces")), // Claimed workspace (null = anonymous)
420
- metadata: v.optional(v.any()),
421
- timestamp: v.number(),
422
- })
423
- .index("by_event", ["event"])
424
- .index("by_timestamp", ["timestamp"])
425
- .index("by_provider", ["provider"])
426
- .index("by_workspaceId", ["workspaceId"])
427
- .index("by_identifier", ["identifier"]),
428
-
429
- // ============================================
430
- // FUNNEL EVENTS — canonical conversion truth
431
- // install -> first_run -> register_owner -> verify_code -> first_call_api_success
432
- // ============================================
433
- funnelEvents: defineTable({
434
- event: v.string(), // one of FUNNEL_EVENTS (see convex/funnel.ts)
435
- classification: v.string(), // "human" | "ci" | "bot" | "internal"
436
- workspaceId: v.optional(v.id("workspaces")),
437
- fingerprint: v.optional(v.string()),
438
- sessionToken: v.optional(v.string()),
439
- email: v.optional(v.string()),
440
- userAgent: v.optional(v.string()),
441
- mcpClient: v.optional(v.string()),
442
- platform: v.optional(v.string()),
443
- version: v.optional(v.string()),
444
- dedupeKey: v.optional(v.string()), // for first-time events (install/first_run/first_call)
445
- props: v.optional(v.any()),
446
- timestamp: v.number(),
447
- })
448
- .index("by_event", ["event"])
449
- .index("by_classification", ["classification"])
450
- .index("by_workspaceId", ["workspaceId"])
451
- .index("by_fingerprint", ["fingerprint"])
452
- .index("by_dedupeKey", ["dedupeKey"])
453
- .index("by_timestamp", ["timestamp"])
454
- .index("by_event_timestamp", ["event", "timestamp"]),
455
-
456
- // MCP Server telemetry (anonymous usage tracking)
457
- telemetry: defineTable({
458
- type: v.string(), // "startup", "search", "execute", "discovery"
459
- query: v.optional(v.string()),
460
- apiId: v.optional(v.string()),
461
- resultCount: v.optional(v.number()),
462
- responseTimeMs: v.optional(v.number()),
463
- version: v.string(),
464
- platform: v.string(),
465
- nodeVersion: v.string(),
466
- timestamp: v.number(),
467
- })
468
- .index("by_type", ["type"])
469
- .index("by_timestamp", ["timestamp"]),
470
-
471
- // ============================================
472
- // SELF-SERVICE DIRECT CALL TABLES
473
- // ============================================
474
-
475
- // Provider Direct Call configuration (master key, limits, pricing)
476
- providerDirectCall: defineTable({
477
- providerId: v.id("providers"),
478
- workspaceId: v.optional(v.id("workspaces")), // Parallel workspace link
479
- apiId: v.optional(v.id("providerAPIs")),
480
- baseUrl: v.string(),
481
- authType: v.string(), // "bearer" | "basic" | "api_key" | "none"
482
- authHeader: v.string(), // e.g. "Authorization", "X-API-Key"
483
- authPrefix: v.string(), // e.g. "Bearer ", "Basic ", ""
484
- encryptedMasterKey: v.string(),
485
- rateLimitPerUser: v.number(), // requests per minute per user
486
- rateLimitPerDay: v.number(), // requests per day per user
487
- pricePerRequest: v.number(), // in USD cents
488
- status: v.string(), // "draft" | "testing" | "live"
489
- // Customer key passthrough settings
490
- allowCustomerKeys: v.optional(v.boolean()), // Allow agents to pass their own API key (default: true)
491
- requireCustomerKeys: v.optional(v.boolean()), // Require customer key, no master key fallback (default: false)
492
- createdAt: v.number(),
493
- updatedAt: v.number(),
494
- publishedAt: v.optional(v.number()),
495
- })
496
- .index("by_providerId", ["providerId"])
497
- .index("by_workspaceId", ["workspaceId"])
498
- .index("by_apiId", ["apiId"])
499
- .index("by_status", ["status"]),
500
-
501
- // Actions defined by providers for their Direct Call APIs
502
- providerActions: defineTable({
503
- directCallId: v.id("providerDirectCall"),
504
- name: v.string(), // machine name, e.g. "send_sms"
505
- displayName: v.string(), // human-friendly, e.g. "Send SMS"
506
- description: v.string(),
507
- method: v.string(), // "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
508
- path: v.string(), // e.g. "/v1/messages" or "/users/{userId}"
509
- params: v.array(v.object({
510
- name: v.string(),
511
- type: v.string(), // "string" | "number" | "boolean" | "object"
512
- required: v.boolean(),
513
- description: v.string(),
514
- default: v.optional(v.any()),
515
- in: v.string(), // "body" | "query" | "path"
516
- })),
517
- responseMapping: v.array(v.object({
518
- name: v.string(),
519
- path: v.string(), // JSON path, e.g. "data.id" or "results[0].name"
520
- })),
521
- enabled: v.boolean(),
522
- // Confirmation settings for costly actions
523
- requiresConfirmation: v.optional(v.boolean()), // If true, requires user confirmation before executing
524
- estimatedCost: v.optional(v.string()), // Human-readable cost estimate, e.g. "~2-5 SEK per invoice"
525
- createdAt: v.number(),
526
- updatedAt: v.number(),
527
- })
528
- .index("by_directCallId", ["directCallId"])
529
- .index("by_directCallId_name", ["directCallId", "name"]),
530
-
531
- // Usage logs for Direct Call actions
532
- usageLog: defineTable({
533
- userId: v.string(),
534
- providerId: v.id("providers"),
535
- workspaceId: v.optional(v.id("workspaces")), // Parallel workspace link
536
- directCallId: v.id("providerDirectCall"),
537
- actionName: v.string(),
538
- timestamp: v.number(),
539
- success: v.boolean(),
540
- latencyMs: v.number(),
541
- creditsUsed: v.number(), // in USD cents
542
- errorMessage: v.optional(v.string()),
543
- })
544
- .index("by_userId", ["userId"])
545
- .index("by_providerId", ["providerId"])
546
- .index("by_workspaceId", ["workspaceId"])
547
- .index("by_directCallId", ["directCallId"])
548
- .index("by_timestamp", ["timestamp"])
549
- .index("by_userId_providerId", ["userId", "providerId"])
550
- .index("by_userId_timestamp", ["userId", "timestamp"]),
551
-
552
- // ============================================
553
- // API LOGS (workspace/consumer view)
554
- // ============================================
555
-
556
- apiLogs: defineTable({
557
- workspaceId: v.id("workspaces"),
558
- sessionToken: v.string(),
559
- subagentId: v.optional(v.string()),
560
- provider: v.string(),
561
- action: v.string(),
562
- status: v.union(v.literal("success"), v.literal("error")),
563
- latencyMs: v.number(),
564
- errorMessage: v.optional(v.string()),
565
- direction: v.optional(v.string()), // "outbound" (I called) or "inbound" (someone called my API)
566
- callerWorkspaceId: v.optional(v.string()), // who made the call (for inbound logs)
567
- createdAt: v.number(),
568
- })
569
- .index("by_workspaceId", ["workspaceId"])
570
- .index("by_createdAt", ["createdAt"])
571
- .index("by_workspaceId_createdAt", ["workspaceId", "createdAt"])
572
- .index("by_subagentId", ["subagentId"])
573
- .index("by_provider", ["provider"]),
574
-
575
- // ============================================
576
- // WAITLIST (for Direct Call provider leads)
577
- // ============================================
578
-
579
- waitlist: defineTable({
580
- email: v.string(),
581
- type: v.string(), // "provider" | "agent" | "general"
582
- source: v.optional(v.string()), // "landing", "docs", etc.
583
- createdAt: v.number(),
584
- })
585
- .index("by_email", ["email"])
586
- .index("by_type", ["type"]),
587
-
588
- // ============================================
589
- // CAPABILITY LAYER (abstraction over providers)
590
- // ============================================
591
-
592
- // Capability definitions (sms, email, invoice, search, etc.)
593
- capabilities: defineTable({
594
- id: v.string(), // "sms", "email", "invoice"
595
- name: v.string(), // "SMS Messaging"
596
- description: v.string(),
597
- category: v.string(), // "communication", "business", "ai"
598
- standardParams: v.array(v.object({
599
- name: v.string(),
600
- type: v.string(), // "string" | "number" | "boolean"
601
- required: v.boolean(),
602
- description: v.string(),
603
- default: v.optional(v.any()),
604
- })),
605
- createdAt: v.number(),
606
- updatedAt: v.number(),
607
- })
608
- .index("by_capability_id", ["id"])
609
- .index("by_category", ["category"]),
610
-
611
- // Provider → Capability mappings (which providers offer which capabilities)
612
- providerCapabilities: defineTable({
613
- providerId: v.string(), // "46elks", "twilio"
614
- capabilityId: v.string(), // "sms"
615
- priority: v.number(), // 1 = primary, 2 = fallback
616
- regions: v.array(v.string()), // ["SE", "EU", "US"]
617
- pricePerUnit: v.number(), // in smallest currency unit (cents/öre)
618
- currency: v.string(), // "SEK", "USD"
619
- avgLatencyMs: v.number(),
620
- paramMapping: v.any(), // Record<string, string> - capability param → provider param
621
- enabled: v.boolean(),
622
- healthStatus: v.string(), // "healthy" | "degraded" | "down"
623
- lastHealthCheck: v.optional(v.number()),
624
- createdAt: v.number(),
625
- updatedAt: v.number(),
626
- })
627
- .index("by_providerId", ["providerId"])
628
- .index("by_capabilityId", ["capabilityId"])
629
- .index("by_capabilityId_enabled", ["capabilityId", "enabled"])
630
- .index("by_healthStatus", ["healthStatus"]),
631
-
632
- // Capability usage logs (for analytics and billing)
633
- capabilityLogs: defineTable({
634
- capabilityId: v.string(),
635
- providerId: v.string(),
636
- userId: v.string(),
637
- action: v.string(),
638
- success: v.boolean(),
639
- fallbackUsed: v.boolean(),
640
- fallbackReason: v.optional(v.string()),
641
- latencyMs: v.number(),
642
- cost: v.number(),
643
- currency: v.string(),
644
- timestamp: v.number(),
645
- })
646
- .index("by_capabilityId", ["capabilityId"])
647
- .index("by_providerId", ["providerId"])
648
- .index("by_userId", ["userId"])
649
- .index("by_timestamp", ["timestamp"]),
650
-
651
- // ============================================
652
- // WEBHOOKS
653
- // ============================================
654
-
655
- webhooks: defineTable({
656
- workspaceId: v.id("workspaces"),
657
- url: v.string(),
658
- events: v.array(v.string()),
659
- secret: v.string(), // For signature verification
660
- enabled: v.boolean(),
661
- lastTriggeredAt: v.optional(v.number()),
662
- lastStatus: v.optional(v.string()), // "success" | "failed"
663
- failCount: v.number(),
664
- createdAt: v.number(),
665
- })
666
- .index("by_workspaceId", ["workspaceId"]),
667
-
668
- // ============================================
669
- // BYOK - BRING YOUR OWN KEY
670
- // ============================================
671
-
672
- // User-provided API keys for providers
673
- providerKeys: defineTable({
674
- workspaceId: v.id("workspaces"),
675
- provider: v.string(), // "brave_search", "openrouter", etc.
676
- encryptedKey: v.string(), // Base64 encoded for MVP
677
- keyHint: v.string(), // Last 4 chars for display
678
- isCustom: v.boolean(), // true if custom provider (not built-in)
679
- customConfig: v.optional(v.object({
680
- baseUrl: v.string(),
681
- authType: v.string(), // "bearer", "api_key", "basic"
682
- authHeader: v.optional(v.string()), // e.g. "X-API-Key"
683
- })),
684
- createdAt: v.number(),
685
- updatedAt: v.number(),
686
- })
687
- .index("by_workspaceId", ["workspaceId"])
688
- .index("by_provider", ["workspaceId", "provider"]),
689
-
690
- // ============================================
691
- // EARN PROGRESS TRACKING
692
- // ============================================
693
-
694
- earnProgress: defineTable({
695
- workspaceId: v.id("workspaces"),
696
-
697
- // Usage tasks
698
- firstDirectCall: v.boolean(),
699
- firstDirectCallAt: v.optional(v.number()),
700
-
701
- apisUsed: v.array(v.string()), // Track unique provider/action combos
702
- apisUsedComplete: v.boolean(),
703
-
704
- agentListed: v.boolean(),
705
- agentListedAt: v.optional(v.number()),
706
-
707
- apiListed: v.boolean(),
708
- apiListedAt: v.optional(v.number()),
709
-
710
- byokSetup: v.boolean(),
711
- byokSetupAt: v.optional(v.number()),
712
-
713
- // Growth tasks
714
- githubStarred: v.boolean(),
715
- githubStarredAt: v.optional(v.number()),
716
-
717
- twitterFollowed: v.boolean(),
718
- twitterFollowedAt: v.optional(v.number()),
719
-
720
- // Referrals (tracked separately but stored here for convenience)
721
- referralCount: v.number(),
722
-
723
- // Calculated total
724
- totalEarned: v.number(),
725
-
726
- createdAt: v.number(),
727
- updatedAt: v.number(),
728
- })
729
- .index("by_workspaceId", ["workspaceId"]),
730
-
731
- // ============================================
732
- // FEEDBACK SYSTEM
733
- // ============================================
734
-
735
- // ============================================
736
- // CHAIN ORCHESTRATION TABLES
737
- // ============================================
738
-
739
- // Chain executions (main orchestration record)
740
- chains: defineTable({
741
- workspaceId: v.id("workspaces"),
742
- // Chain definition
743
- steps: v.array(v.any()), // Array of step definitions (raw, unresolved)
744
- // Execution state
745
- status: v.union(
746
- v.literal("pending"),
747
- v.literal("running"),
748
- v.literal("completed"),
749
- v.literal("failed"),
750
- v.literal("paused")
751
- ),
752
- currentStep: v.number(), // Index of current step (0-based)
753
- // Results storage
754
- results: v.any(), // Record<stepId, result>
755
- // Error tracking
756
- error: v.optional(v.object({
757
- stepId: v.string(),
758
- code: v.string(),
759
- message: v.string(),
760
- retryAfter: v.optional(v.number()),
761
- })),
762
- // Execution options
763
- continueOnError: v.optional(v.boolean()),
764
- timeout: v.optional(v.number()), // ms
765
- // Resume capability
766
- resumeToken: v.optional(v.string()),
767
- canResume: v.optional(v.boolean()),
768
- // Cost tracking
769
- totalCostCents: v.optional(v.number()),
770
- totalLatencyMs: v.optional(v.number()),
771
- // Timestamps
772
- createdAt: v.number(),
773
- startedAt: v.optional(v.number()),
774
- completedAt: v.optional(v.number()),
775
- })
776
- .index("by_workspaceId", ["workspaceId"])
777
- .index("by_status", ["status"])
778
- .index("by_workspaceId_status", ["workspaceId", "status"])
779
- .index("by_resumeToken", ["resumeToken"]),
780
-
781
- // Chain templates (reusable chain definitions)
782
- chainTemplates: defineTable({
783
- workspaceId: v.id("workspaces"),
784
- name: v.string(),
785
- description: v.optional(v.string()),
786
- // Input schema for the template
787
- inputs: v.optional(v.any()), // JSON Schema for inputs
788
- // Chain definition
789
- chain: v.array(v.any()), // Array of step definitions
790
- // Usage tracking
791
- useCount: v.optional(v.number()),
792
- lastUsedAt: v.optional(v.number()),
793
- // Timestamps
794
- createdAt: v.number(),
795
- updatedAt: v.number(),
796
- })
797
- .index("by_workspaceId", ["workspaceId"])
798
- .index("by_name", ["workspaceId", "name"]),
799
-
800
- // Chain step executions (detailed trace per step)
801
- chainExecutions: defineTable({
802
- chainId: v.id("chains"),
803
- stepId: v.string(), // The id from step definition
804
- stepIndex: v.number(), // Position in chain
805
- // Execution state
806
- status: v.union(
807
- v.literal("pending"),
808
- v.literal("running"),
809
- v.literal("completed"),
810
- v.literal("failed"),
811
- v.literal("skipped")
812
- ),
813
- // I/O
814
- input: v.optional(v.any()), // Resolved params sent to provider
815
- output: v.optional(v.any()), // Result from provider
816
- // Metrics
817
- latencyMs: v.optional(v.number()),
818
- costCents: v.optional(v.number()),
819
- // Error info
820
- error: v.optional(v.object({
821
- code: v.string(),
822
- message: v.string(),
823
- retryCount: v.optional(v.number()),
824
- })),
825
- // Parallel execution tracking
826
- parallelGroup: v.optional(v.string()), // Group ID if part of parallel batch
827
- // Timestamps
828
- createdAt: v.number(),
829
- startedAt: v.optional(v.number()),
830
- completedAt: v.optional(v.number()),
831
- })
832
- .index("by_chainId", ["chainId"])
833
- .index("by_chainId_stepId", ["chainId", "stepId"])
834
- .index("by_chainId_stepIndex", ["chainId", "stepIndex"]),
835
-
836
- // User feedback with voting
837
- feedback: defineTable({
838
- workspaceId: v.id("workspaces"),
839
- type: v.union(v.literal("bug"), v.literal("feature"), v.literal("general")),
840
- content: v.string(),
841
- votes: v.number(),
842
- votedBy: v.array(v.string()), // workspace IDs that voted
843
- status: v.union(v.literal("new"), v.literal("reviewing"), v.literal("planned"), v.literal("shipped")),
844
- createdAt: v.number(),
845
- })
846
- .index("by_workspaceId", ["workspaceId"])
847
- .index("by_type", ["type"])
848
- .index("by_status", ["status"])
849
- .index("by_votes", ["votes"])
850
- .index("by_createdAt", ["createdAt"]),
851
-
852
- // ============================================
853
- // WORKSPACE SETTINGS (Gateway routing config)
854
- // ============================================
855
-
856
- workspaceSettings: defineTable({
857
- workspaceId: v.id("workspaces"),
858
- // Routing preferences
859
- routingMode: v.string(), // "best_price" | "highest_quality" | "fastest" | "balanced"
860
- defaultModel: v.optional(v.string()), // e.g. "anthropic/claude-sonnet-4-6"
861
- // Budget controls
862
- maxPricePerMTokens: v.optional(v.float64()), // max $/million tokens, null = no limit
863
- monthlyBudgetLimit: v.optional(v.float64()), // monthly budget in USD, null = no limit
864
- // Provider preferences
865
- preferredProviders: v.optional(v.array(v.string())), // e.g. ["groq", "mistral", "together"]
866
- blockedProviders: v.optional(v.array(v.string())), // providers to never use
867
- // Fallback
868
- allowOpenRouterFallback: v.optional(v.boolean()), // default true
869
- // Timestamps
870
- createdAt: v.number(),
871
- updatedAt: v.number(),
872
- })
873
- .index("by_workspaceId", ["workspaceId"]),
874
-
875
- // ============================================
876
- // MOU SIGNATURES
877
- // ============================================
878
-
879
- mouDocuments: defineTable({
880
- partnerId: v.string(), // e.g., "apilayer"
881
- partnerName: v.string(),
882
- partnerEmail: v.string(),
883
- partnerRepresentative: v.optional(v.string()),
884
- documentHtml: v.optional(v.string()),
885
- sections: v.optional(v.any()), // Alternative document format
886
- status: v.string(), // "pending" | "signed"
887
- signedAt: v.optional(v.number()),
888
- signatureDataUrl: v.optional(v.string()), // base64 signature image
889
- signerName: v.optional(v.string()),
890
- signerTitle: v.optional(v.string()),
891
- signerIp: v.optional(v.string()),
892
- createdAt: v.number(),
893
- })
894
- .index("by_partnerId", ["partnerId"])
895
- .index("by_status", ["status"]),
896
-
897
- // ═══════════════════════════════════════════════════════════════
898
- // NURTURE - Workspace lifecycle + email nurture state
899
- // ═══════════════════════════════════════════════════════════════
900
- nurture: defineTable({
901
- workspaceId: v.id("workspaces"),
902
- email: v.optional(v.string()), // mirrored for easy dedupe
903
- stage: v.union(
904
- v.literal("new"), // <48h since signup, no activity yet
905
- v.literal("activating"), // seen some discovery, no calls
906
- v.literal("active"), // has made API calls recently
907
- v.literal("power"), // >50 calls in last 14d
908
- v.literal("dormant"), // no activity 7d+
909
- v.literal("lost"), // no activity 30d+
910
- v.literal("partner-locked"), // partner workspace — never nurture
911
- v.literal("excluded") // explicit opt-out / internal / test
912
- ),
913
- lastActivityAt: v.optional(v.number()),
914
- emailsSent: v.number(), // total nurture emails sent
915
- lastEmailSentAt: v.optional(v.number()),
916
- lastEmailKind: v.optional(v.string()), // "welcome", "try-discover", "first-call", "upgrade", "reactivate-7d", "reactivate-30d", "power-upgrade"
917
- unsubscribed: v.boolean(),
918
- notes: v.optional(v.string()),
919
- createdAt: v.number(),
920
- updatedAt: v.number(),
921
- })
922
- .index("by_workspaceId", ["workspaceId"])
923
- .index("by_email", ["email"])
924
- .index("by_stage", ["stage"])
925
- .index("by_lastActivityAt", ["lastActivityAt"]),
926
- });