@nordsym/apiclaw 1.3.7 → 1.3.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 (189) hide show
  1. package/README.md +420 -200
  2. package/convex/_generated/api.d.ts +4 -0
  3. package/convex/agents.ts +403 -0
  4. package/convex/directCall.ts +80 -0
  5. package/convex/earnProgress.ts +753 -0
  6. package/convex/logs.ts +17 -0
  7. package/convex/providerKeys.ts +82 -2
  8. package/convex/schema.ts +71 -2
  9. package/convex/workspaces.ts +84 -2
  10. package/dist/adapters/base.d.ts +112 -0
  11. package/dist/adapters/base.d.ts.map +1 -0
  12. package/dist/adapters/base.js +247 -0
  13. package/dist/adapters/base.js.map +1 -0
  14. package/dist/adapters/claude-desktop.d.ts +12 -0
  15. package/dist/adapters/claude-desktop.d.ts.map +1 -0
  16. package/dist/adapters/claude-desktop.js +36 -0
  17. package/dist/adapters/claude-desktop.js.map +1 -0
  18. package/dist/adapters/cline.d.ts +20 -0
  19. package/dist/adapters/cline.d.ts.map +1 -0
  20. package/dist/adapters/cline.js +77 -0
  21. package/dist/adapters/cline.js.map +1 -0
  22. package/dist/adapters/continue.d.ts +26 -0
  23. package/dist/adapters/continue.d.ts.map +1 -0
  24. package/dist/adapters/continue.js +68 -0
  25. package/dist/adapters/continue.js.map +1 -0
  26. package/dist/adapters/cursor.d.ts +12 -0
  27. package/dist/adapters/cursor.d.ts.map +1 -0
  28. package/dist/adapters/cursor.js +38 -0
  29. package/dist/adapters/cursor.js.map +1 -0
  30. package/dist/adapters/custom.d.ts +47 -0
  31. package/dist/adapters/custom.d.ts.map +1 -0
  32. package/dist/adapters/custom.js +146 -0
  33. package/dist/adapters/custom.js.map +1 -0
  34. package/dist/adapters/detect.d.ts +69 -0
  35. package/dist/adapters/detect.d.ts.map +1 -0
  36. package/dist/adapters/detect.js +158 -0
  37. package/dist/adapters/detect.js.map +1 -0
  38. package/dist/adapters/index.d.ts +21 -0
  39. package/dist/adapters/index.d.ts.map +1 -0
  40. package/dist/adapters/index.js +23 -0
  41. package/dist/adapters/index.js.map +1 -0
  42. package/dist/adapters/windsurf.d.ts +12 -0
  43. package/dist/adapters/windsurf.d.ts.map +1 -0
  44. package/dist/adapters/windsurf.js +39 -0
  45. package/dist/adapters/windsurf.js.map +1 -0
  46. package/dist/bin.d.ts +9 -0
  47. package/dist/bin.d.ts.map +1 -0
  48. package/dist/bin.js +19 -0
  49. package/dist/bin.js.map +1 -0
  50. package/dist/cli/commands/doctor.d.ts +34 -0
  51. package/dist/cli/commands/doctor.d.ts.map +1 -0
  52. package/dist/cli/commands/doctor.js +312 -0
  53. package/dist/cli/commands/doctor.js.map +1 -0
  54. package/dist/cli/commands/index.d.ts +9 -0
  55. package/dist/cli/commands/index.d.ts.map +1 -0
  56. package/dist/cli/commands/index.js +9 -0
  57. package/dist/cli/commands/index.js.map +1 -0
  58. package/dist/cli/commands/restore.d.ts +50 -0
  59. package/dist/cli/commands/restore.d.ts.map +1 -0
  60. package/dist/cli/commands/restore.js +260 -0
  61. package/dist/cli/commands/restore.js.map +1 -0
  62. package/dist/cli/commands/setup.d.ts +19 -0
  63. package/dist/cli/commands/setup.d.ts.map +1 -0
  64. package/dist/cli/commands/setup.js +206 -0
  65. package/dist/cli/commands/setup.js.map +1 -0
  66. package/dist/cli/commands/uninstall.d.ts +37 -0
  67. package/dist/cli/commands/uninstall.d.ts.map +1 -0
  68. package/dist/cli/commands/uninstall.js +189 -0
  69. package/dist/cli/commands/uninstall.js.map +1 -0
  70. package/dist/cli/index.d.ts +7 -0
  71. package/dist/cli/index.d.ts.map +1 -0
  72. package/dist/cli/index.js +97 -0
  73. package/dist/cli/index.js.map +1 -0
  74. package/dist/discovery.d.ts +6 -2
  75. package/dist/discovery.d.ts.map +1 -1
  76. package/dist/discovery.js +296 -2
  77. package/dist/discovery.js.map +1 -1
  78. package/dist/enterprise/env.d.ts +56 -0
  79. package/dist/enterprise/env.d.ts.map +1 -0
  80. package/dist/enterprise/env.js +124 -0
  81. package/dist/enterprise/env.js.map +1 -0
  82. package/dist/enterprise/index.d.ts +7 -0
  83. package/dist/enterprise/index.d.ts.map +1 -0
  84. package/dist/enterprise/index.js +7 -0
  85. package/dist/enterprise/index.js.map +1 -0
  86. package/dist/enterprise/script-generator.d.ts +32 -0
  87. package/dist/enterprise/script-generator.d.ts.map +1 -0
  88. package/dist/enterprise/script-generator.js +461 -0
  89. package/dist/enterprise/script-generator.js.map +1 -0
  90. package/dist/execute.d.ts +21 -0
  91. package/dist/execute.d.ts.map +1 -1
  92. package/dist/execute.js +231 -0
  93. package/dist/execute.js.map +1 -1
  94. package/dist/index.js +79 -7
  95. package/dist/index.js.map +1 -1
  96. package/dist/stripe.d.ts +1 -1
  97. package/dist/stripe.js +1 -1
  98. package/dist/stripe.js.map +1 -1
  99. package/dist/types.d.ts +29 -0
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/ui/colors.d.ts +111 -0
  102. package/dist/ui/colors.d.ts.map +1 -0
  103. package/dist/ui/colors.js +185 -0
  104. package/dist/ui/colors.js.map +1 -0
  105. package/dist/ui/errors.d.ts +69 -0
  106. package/dist/ui/errors.d.ts.map +1 -0
  107. package/dist/ui/errors.js +334 -0
  108. package/dist/ui/errors.js.map +1 -0
  109. package/dist/ui/index.d.ts +10 -0
  110. package/dist/ui/index.d.ts.map +1 -0
  111. package/dist/ui/index.js +14 -0
  112. package/dist/ui/index.js.map +1 -0
  113. package/dist/ui/prompts.d.ts +88 -0
  114. package/dist/ui/prompts.d.ts.map +1 -0
  115. package/dist/ui/prompts.js +295 -0
  116. package/dist/ui/prompts.js.map +1 -0
  117. package/dist/ui/spinner.d.ts +112 -0
  118. package/dist/ui/spinner.d.ts.map +1 -0
  119. package/dist/ui/spinner.js +229 -0
  120. package/dist/ui/spinner.js.map +1 -0
  121. package/dist/utils/backup.d.ts +48 -0
  122. package/dist/utils/backup.d.ts.map +1 -0
  123. package/dist/utils/backup.js +182 -0
  124. package/dist/utils/backup.js.map +1 -0
  125. package/dist/utils/config.d.ts +80 -0
  126. package/dist/utils/config.d.ts.map +1 -0
  127. package/dist/utils/config.js +221 -0
  128. package/dist/utils/config.js.map +1 -0
  129. package/dist/utils/os.d.ts +45 -0
  130. package/dist/utils/os.d.ts.map +1 -0
  131. package/dist/utils/os.js +106 -0
  132. package/dist/utils/os.js.map +1 -0
  133. package/dist/utils/paths.d.ts +38 -0
  134. package/dist/utils/paths.d.ts.map +1 -0
  135. package/dist/utils/paths.js +160 -0
  136. package/dist/utils/paths.js.map +1 -0
  137. package/docs/PRD-BILLING.md +226 -0
  138. package/docs/PRD-EARN-SYSTEM.md +261 -0
  139. package/docs/PRD-MCP-AUTO-SETUP.md +623 -0
  140. package/docs/enterprise-deployment.md +728 -0
  141. package/landing/next.config.mjs +14 -0
  142. package/landing/public/stats.json +4 -2
  143. package/landing/scripts/generate-stats.js +12 -0
  144. package/landing/src/app/api/workspace-auth/magic-link/route.ts +6 -3
  145. package/landing/src/app/auth/verify/page.tsx +11 -4
  146. package/landing/src/app/docs/page.tsx +1 -1
  147. package/landing/src/app/join/page.tsx +49 -0
  148. package/landing/src/app/login/page.tsx +7 -1
  149. package/landing/src/app/page.tsx +13 -28
  150. package/landing/src/app/providers/register/page.tsx +1 -1
  151. package/landing/src/app/workspace/page.tsx +483 -710
  152. package/landing/src/components/CheckoutButton.tsx +1 -1
  153. package/landing/src/components/EarnCreditsTab.tsx +842 -0
  154. package/landing/src/lib/stats.json +3 -1
  155. package/package.json +9 -2
  156. package/src/adapters/base.ts +363 -0
  157. package/src/adapters/claude-desktop.ts +41 -0
  158. package/src/adapters/cline.ts +88 -0
  159. package/src/adapters/continue.ts +91 -0
  160. package/src/adapters/cursor.ts +43 -0
  161. package/src/adapters/custom.ts +188 -0
  162. package/src/adapters/detect.ts +202 -0
  163. package/src/adapters/index.ts +47 -0
  164. package/src/adapters/windsurf.ts +44 -0
  165. package/src/bin.ts +19 -0
  166. package/src/cli/commands/doctor.ts +367 -0
  167. package/src/cli/commands/index.ts +9 -0
  168. package/src/cli/commands/restore.ts +333 -0
  169. package/src/cli/commands/setup.ts +276 -0
  170. package/src/cli/commands/uninstall.ts +240 -0
  171. package/src/cli/index.ts +107 -0
  172. package/src/discovery.ts +328 -3
  173. package/src/enterprise/env.ts +156 -0
  174. package/src/enterprise/index.ts +7 -0
  175. package/src/enterprise/script-generator.ts +481 -0
  176. package/src/execute.ts +256 -0
  177. package/src/index.ts +85 -7
  178. package/src/stripe.ts +1 -1
  179. package/src/types.ts +32 -0
  180. package/src/ui/colors.ts +219 -0
  181. package/src/ui/errors.ts +394 -0
  182. package/src/ui/index.ts +17 -0
  183. package/src/ui/prompts.ts +390 -0
  184. package/src/ui/spinner.ts +325 -0
  185. package/src/utils/backup.ts +224 -0
  186. package/src/utils/config.ts +315 -0
  187. package/src/utils/os.ts +124 -0
  188. package/src/utils/paths.ts +203 -0
  189. package/landing/tsconfig.tsbuildinfo +0 -1
package/convex/logs.ts CHANGED
@@ -17,6 +17,7 @@ export const createLog = mutation({
17
17
  status: v.union(v.literal("success"), v.literal("error")),
18
18
  latencyMs: v.number(),
19
19
  errorMessage: v.optional(v.string()),
20
+ subagentId: v.optional(v.string()), // from X-APIClaw-Subagent header
20
21
  },
21
22
  handler: async (ctx, args) => {
22
23
  // Verify session and get workspace
@@ -33,6 +34,7 @@ export const createLog = mutation({
33
34
  return await ctx.db.insert("apiLogs", {
34
35
  workspaceId: session.workspaceId,
35
36
  sessionToken: args.token,
37
+ subagentId: args.subagentId,
36
38
  provider: args.provider,
37
39
  action: args.action,
38
40
  status: args.status,
@@ -56,11 +58,13 @@ export const createLogInternal = mutation({
56
58
  status: v.union(v.literal("success"), v.literal("error")),
57
59
  latencyMs: v.number(),
58
60
  errorMessage: v.optional(v.string()),
61
+ subagentId: v.optional(v.string()), // from X-APIClaw-Subagent header
59
62
  },
60
63
  handler: async (ctx, args) => {
61
64
  return await ctx.db.insert("apiLogs", {
62
65
  workspaceId: args.workspaceId,
63
66
  sessionToken: args.sessionToken,
67
+ subagentId: args.subagentId,
64
68
  provider: args.provider,
65
69
  action: args.action,
66
70
  status: args.status,
@@ -85,11 +89,13 @@ export const getLogs = query({
85
89
  cursor: v.optional(v.number()), // createdAt timestamp for pagination
86
90
  status: v.optional(v.union(v.literal("success"), v.literal("error"), v.literal("all"))),
87
91
  provider: v.optional(v.string()),
92
+ subagentId: v.optional(v.string()), // filter by subagent
88
93
  },
89
94
  handler: async (ctx, args) => {
90
95
  const limit = args.limit ?? 50;
91
96
  const status = args.status ?? "all";
92
97
  const provider = args.provider;
98
+ const subagentId = args.subagentId;
93
99
  const cursor = args.cursor;
94
100
 
95
101
  // Verify session
@@ -127,6 +133,16 @@ export const getLogs = query({
127
133
  filteredLogs = filteredLogs.filter((log) => log.provider === provider);
128
134
  }
129
135
 
136
+ // Filter by subagent
137
+ if (subagentId) {
138
+ if (subagentId === "main") {
139
+ // Main agent calls (no subagentId)
140
+ filteredLogs = filteredLogs.filter((log) => !log.subagentId);
141
+ } else {
142
+ filteredLogs = filteredLogs.filter((log) => log.subagentId === subagentId);
143
+ }
144
+ }
145
+
130
146
  const hasMore = filteredLogs.length > limit;
131
147
  const logs = filteredLogs.slice(0, limit);
132
148
 
@@ -138,6 +154,7 @@ export const getLogs = query({
138
154
  status: log.status,
139
155
  latencyMs: log.latencyMs,
140
156
  errorMessage: log.errorMessage,
157
+ subagentId: log.subagentId || null,
141
158
  createdAt: log.createdAt,
142
159
  })),
143
160
  hasMore,
@@ -64,6 +64,8 @@ export const addKey = mutation({
64
64
  const encryptedKey = encryptKey(args.apiKey);
65
65
  const keyHint = getKeyHint(args.apiKey);
66
66
 
67
+ let isFirstKey = false;
68
+
67
69
  if (existingKey) {
68
70
  // Update existing key
69
71
  await ctx.db.patch(existingKey._id, {
@@ -71,7 +73,6 @@ export const addKey = mutation({
71
73
  keyHint,
72
74
  updatedAt: now,
73
75
  });
74
- return { success: true, action: "updated" };
75
76
  } else {
76
77
  // Create new key
77
78
  await ctx.db.insert("providerKeys", {
@@ -83,11 +84,90 @@ export const addKey = mutation({
83
84
  createdAt: now,
84
85
  updatedAt: now,
85
86
  });
86
- return { success: true, action: "created" };
87
+
88
+ // Check if this is the first BYOK key for earn progress
89
+ const allKeys = await ctx.db
90
+ .query("providerKeys")
91
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
92
+ .collect();
93
+
94
+ // If this is the only key (the one we just created), mark BYOK setup
95
+ if (allKeys.length === 1) {
96
+ isFirstKey = true;
97
+ // Import and call markByokSetup
98
+ const earnProgress = await ctx.db
99
+ .query("earnProgress")
100
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
101
+ .first();
102
+
103
+ if (earnProgress && !earnProgress.byokSetup) {
104
+ const newTotal = calculateEarnTotal({ ...earnProgress, byokSetup: true });
105
+ await ctx.db.patch(earnProgress._id, {
106
+ byokSetup: true,
107
+ byokSetupAt: now,
108
+ totalEarned: newTotal,
109
+ updatedAt: now,
110
+ });
111
+ // Add 5 calls to workspace limit
112
+ const workspace = await ctx.db.get(workspaceId);
113
+ if (workspace) {
114
+ await ctx.db.patch(workspaceId, {
115
+ usageLimit: workspace.usageLimit + 5,
116
+ updatedAt: now,
117
+ });
118
+ }
119
+ } else if (!earnProgress) {
120
+ // Create earn progress with byokSetup
121
+ await ctx.db.insert("earnProgress", {
122
+ workspaceId,
123
+ firstDirectCall: false,
124
+ apisUsed: [],
125
+ apisUsedComplete: false,
126
+ agentListed: false,
127
+ apiListed: false,
128
+ byokSetup: true,
129
+ byokSetupAt: now,
130
+ githubStarred: false,
131
+ twitterFollowed: false,
132
+ referralCount: 0,
133
+ totalEarned: 5, // BYOK reward
134
+ createdAt: now,
135
+ updatedAt: now,
136
+ });
137
+ // Add 5 calls to workspace limit
138
+ const workspace = await ctx.db.get(workspaceId);
139
+ if (workspace) {
140
+ await ctx.db.patch(workspaceId, {
141
+ usageLimit: workspace.usageLimit + 5,
142
+ updatedAt: now,
143
+ });
144
+ }
145
+ }
146
+ }
87
147
  }
148
+
149
+ return {
150
+ success: true,
151
+ action: existingKey ? "updated" : "created",
152
+ earnedByok: isFirstKey,
153
+ };
88
154
  },
89
155
  });
90
156
 
157
+ // Helper to calculate earn total (duplicated to avoid circular import)
158
+ function calculateEarnTotal(progress: any): number {
159
+ let total = 0;
160
+ if (progress.firstDirectCall) total += 15;
161
+ if (progress.apisUsedComplete) total += 10;
162
+ if (progress.agentListed) total += 10;
163
+ if (progress.apiListed) total += 10;
164
+ if (progress.byokSetup) total += 5;
165
+ if (progress.githubStarred) total += 10;
166
+ if (progress.twitterFollowed) total += 5;
167
+ total += (progress.referralCount || 0) * 10;
168
+ return total;
169
+ }
170
+
91
171
  // ============================================
92
172
  // REMOVE KEY
93
173
  // ============================================
package/convex/schema.ts CHANGED
@@ -49,19 +49,32 @@ export default defineSchema({
49
49
  tier: v.string(), // "free" | "pro" | "enterprise"
50
50
  usageCount: v.number(), // total API calls made
51
51
  usageLimit: v.number(), // max API calls for tier
52
+ // Main agent identification
53
+ mainAgentId: v.optional(v.string()), // UUID, auto-generated on first call
54
+ mainAgentName: v.optional(v.string()), // Auto-generated name (e.g., "Crimson Phoenix")
52
55
  // Stripe billing fields
53
56
  stripeCustomerId: v.optional(v.string()),
54
57
  stripeSubscriptionId: v.optional(v.string()),
55
58
  billingPlan: v.optional(v.string()), // "free" | "usage_based" | "starter" | "pro" | "scale"
56
59
  creditBalance: v.optional(v.number()), // prepaid credits in cents
57
60
  lastBillingDate: v.optional(v.number()),
61
+ // Payment method fields
62
+ hasPaymentMethod: v.optional(v.boolean()),
63
+ paymentMethodType: v.optional(v.string()),
64
+ cardBrand: v.optional(v.string()),
65
+ cardLast4: v.optional(v.string()),
66
+ // Referral fields
67
+ referralCode: v.optional(v.string()), // CLAW-XXXXXX format
68
+ referredBy: v.optional(v.id("workspaces")), // who referred this user
58
69
  createdAt: v.number(),
59
70
  updatedAt: v.number(),
60
71
  })
61
72
  .index("by_email", ["email"])
62
73
  .index("by_stripeCustomerId", ["stripeCustomerId"])
63
74
  .index("by_stripeSubscriptionId", ["stripeSubscriptionId"])
64
- .index("by_status", ["status"]),
75
+ .index("by_status", ["status"])
76
+ .index("by_referralCode", ["referralCode"])
77
+ .index("by_mainAgentId", ["mainAgentId"]),
65
78
 
66
79
  // Invoices (Stripe invoice records)
67
80
  invoices: defineTable({
@@ -106,6 +119,19 @@ export default defineSchema({
106
119
  .index("by_sessionToken", ["sessionToken"])
107
120
  .index("by_workspaceId", ["workspaceId"]),
108
121
 
122
+ // Subagent tracking (tasks spawned by main agent)
123
+ subagents: defineTable({
124
+ workspaceId: v.id("workspaces"),
125
+ subagentId: v.string(), // from X-APIClaw-Subagent header
126
+ name: v.optional(v.string()), // optional display name
127
+ callCount: v.number(),
128
+ firstSeenAt: v.number(),
129
+ lastActiveAt: v.number(),
130
+ })
131
+ .index("by_workspaceId", ["workspaceId"])
132
+ .index("by_workspaceId_subagentId", ["workspaceId", "subagentId"])
133
+ .index("by_lastActiveAt", ["lastActiveAt"]),
134
+
109
135
  // Magic links for workspace email verification
110
136
  workspaceMagicLinks: defineTable({
111
137
  email: v.string(),
@@ -383,6 +409,7 @@ export default defineSchema({
383
409
  apiLogs: defineTable({
384
410
  workspaceId: v.id("workspaces"),
385
411
  sessionToken: v.string(),
412
+ subagentId: v.optional(v.string()), // from X-APIClaw-Subagent header
386
413
  provider: v.string(),
387
414
  action: v.string(),
388
415
  status: v.union(v.literal("success"), v.literal("error")),
@@ -392,7 +419,8 @@ export default defineSchema({
392
419
  })
393
420
  .index("by_workspaceId", ["workspaceId"])
394
421
  .index("by_createdAt", ["createdAt"])
395
- .index("by_workspaceId_createdAt", ["workspaceId", "createdAt"]),
422
+ .index("by_workspaceId_createdAt", ["workspaceId", "createdAt"])
423
+ .index("by_subagentId", ["subagentId"]),
396
424
 
397
425
  // ============================================
398
426
  // WAITLIST (for Direct Call provider leads)
@@ -509,6 +537,47 @@ export default defineSchema({
509
537
  .index("by_workspaceId", ["workspaceId"])
510
538
  .index("by_provider", ["workspaceId", "provider"]),
511
539
 
540
+ // ============================================
541
+ // EARN PROGRESS TRACKING
542
+ // ============================================
543
+
544
+ earnProgress: defineTable({
545
+ workspaceId: v.id("workspaces"),
546
+
547
+ // Usage tasks
548
+ firstDirectCall: v.boolean(),
549
+ firstDirectCallAt: v.optional(v.number()),
550
+
551
+ apisUsed: v.array(v.string()), // Track unique provider/action combos
552
+ apisUsedComplete: v.boolean(),
553
+
554
+ agentListed: v.boolean(),
555
+ agentListedAt: v.optional(v.number()),
556
+
557
+ apiListed: v.boolean(),
558
+ apiListedAt: v.optional(v.number()),
559
+
560
+ byokSetup: v.boolean(),
561
+ byokSetupAt: v.optional(v.number()),
562
+
563
+ // Growth tasks
564
+ githubStarred: v.boolean(),
565
+ githubStarredAt: v.optional(v.number()),
566
+
567
+ twitterFollowed: v.boolean(),
568
+ twitterFollowedAt: v.optional(v.number()),
569
+
570
+ // Referrals (tracked separately but stored here for convenience)
571
+ referralCount: v.number(),
572
+
573
+ // Calculated total
574
+ totalEarned: v.number(),
575
+
576
+ createdAt: v.number(),
577
+ updatedAt: v.number(),
578
+ })
579
+ .index("by_workspaceId", ["workspaceId"]),
580
+
512
581
  // ============================================
513
582
  // FEEDBACK SYSTEM
514
583
  // ============================================
@@ -27,13 +27,24 @@ export const createMagicLink = mutation({
27
27
  },
28
28
  });
29
29
 
30
+ // Generate a unique referral code (CLAW-XXXXXX format)
31
+ function generateReferralCode(): string {
32
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
33
+ let code = "";
34
+ for (let i = 0; i < 6; i++) {
35
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
36
+ }
37
+ return `CLAW-${code}`;
38
+ }
39
+
30
40
  // Verify magic link and create workspace + session
31
41
  export const verifyMagicLink = mutation({
32
42
  args: {
33
43
  token: v.string(),
34
44
  fingerprint: v.optional(v.string()),
45
+ referralCode: v.optional(v.string()), // Referral code from signup URL
35
46
  },
36
- handler: async (ctx, { token, fingerprint }) => {
47
+ handler: async (ctx, { token, fingerprint, referralCode }) => {
37
48
  const magicLink = await ctx.db
38
49
  .query("workspaceMagicLinks")
39
50
  .withIndex("by_token", (q) => q.eq("token", token))
@@ -60,20 +71,90 @@ export const verifyMagicLink = mutation({
60
71
  .withIndex("by_email", (q) => q.eq("email", magicLink.email))
61
72
  .first();
62
73
 
74
+ let isNewUser = false;
63
75
  if (!workspace) {
64
- // Create new workspace with free tier
76
+ isNewUser = true;
77
+
78
+ // Generate unique referral code for new user
79
+ let newReferralCode: string;
80
+ let attempts = 0;
81
+ do {
82
+ newReferralCode = generateReferralCode();
83
+ const existing = await ctx.db
84
+ .query("workspaces")
85
+ .withIndex("by_referralCode", (q) => q.eq("referralCode", newReferralCode))
86
+ .first();
87
+ if (!existing) break;
88
+ attempts++;
89
+ } while (attempts < 10);
90
+
91
+ // Create new workspace with free tier + referral code
65
92
  const workspaceId = await ctx.db.insert("workspaces", {
66
93
  email: magicLink.email,
67
94
  status: "active",
68
95
  tier: "free",
69
96
  usageCount: 0,
70
97
  usageLimit: 50, // 50 free API calls
98
+ referralCode: newReferralCode!,
71
99
  createdAt: Date.now(),
72
100
  updatedAt: Date.now(),
73
101
  });
74
102
  workspace = await ctx.db.get(workspaceId);
75
103
  }
76
104
 
105
+ // Process referral for new users
106
+ if (isNewUser && referralCode) {
107
+ // Find referrer by code
108
+ const referrer = await ctx.db
109
+ .query("workspaces")
110
+ .withIndex("by_referralCode", (q) => q.eq("referralCode", referralCode))
111
+ .first();
112
+
113
+ if (referrer && referrer._id !== workspace!._id) {
114
+ // Update new user's referredBy
115
+ await ctx.db.patch(workspace!._id, {
116
+ referredBy: referrer._id,
117
+ updatedAt: Date.now(),
118
+ });
119
+
120
+ // Get or create referrer's earn progress
121
+ let referrerProgress = await ctx.db
122
+ .query("earnProgress")
123
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", referrer._id))
124
+ .first();
125
+
126
+ if (!referrerProgress) {
127
+ await ctx.db.insert("earnProgress", {
128
+ workspaceId: referrer._id,
129
+ firstDirectCall: false,
130
+ apisUsed: [],
131
+ apisUsedComplete: false,
132
+ agentListed: false,
133
+ apiListed: false,
134
+ byokSetup: false,
135
+ githubStarred: false,
136
+ twitterFollowed: false,
137
+ referralCount: 1,
138
+ totalEarned: 10,
139
+ createdAt: Date.now(),
140
+ updatedAt: Date.now(),
141
+ });
142
+ } else {
143
+ await ctx.db.patch(referrerProgress._id, {
144
+ referralCount: referrerProgress.referralCount + 1,
145
+ totalEarned: referrerProgress.totalEarned + 10,
146
+ updatedAt: Date.now(),
147
+ });
148
+ }
149
+
150
+ // Credit referrer with +10 API calls
151
+ await ctx.db.patch(referrer._id, {
152
+ usageLimit: referrer.usageLimit + 10,
153
+ updatedAt: Date.now(),
154
+ });
155
+ }
156
+ }
157
+
77
158
  // Create agent session
78
159
  const sessionToken = generateToken();
79
160
 
@@ -92,6 +173,7 @@ export const verifyMagicLink = mutation({
92
173
  id: workspace!._id,
93
174
  email: workspace!.email,
94
175
  tier: workspace!.tier,
176
+ referralCode: workspace!.referralCode,
95
177
  },
96
178
  };
97
179
  },
@@ -0,0 +1,112 @@
1
+ /**
2
+ * MCP Client Adapter - Base Interface & Abstract Class
3
+ * Defines the contract for all MCP client adapters
4
+ */
5
+ import { MCPClient } from '../utils/paths.js';
6
+ import { Platform } from '../utils/os.js';
7
+ import { MCPConfig, ContinueConfig } from '../utils/config.js';
8
+ export interface ConfigResult {
9
+ success: boolean;
10
+ message: string;
11
+ configPath: string;
12
+ backupPath?: string | null;
13
+ isNew?: boolean;
14
+ alreadyConfigured?: boolean;
15
+ error?: string;
16
+ }
17
+ export interface VerifyResult {
18
+ success: boolean;
19
+ hasConfig: boolean;
20
+ configValid: boolean;
21
+ message: string;
22
+ }
23
+ export interface InstallInfo {
24
+ installed: boolean;
25
+ appPath?: string;
26
+ configExists: boolean;
27
+ configPath: string;
28
+ }
29
+ export interface ConfigureOptions {
30
+ workspaceId?: string;
31
+ serverName?: string;
32
+ force?: boolean;
33
+ }
34
+ /**
35
+ * MCP Client Adapter Interface
36
+ */
37
+ export interface MCPClientAdapter {
38
+ /** Internal client name */
39
+ name: MCPClient | 'custom';
40
+ /** Human-readable display name */
41
+ displayName: string;
42
+ /** Check if the client is installed on the system */
43
+ isInstalled(): Promise<boolean>;
44
+ /** Get the config file path for this client */
45
+ getConfigPath(): string;
46
+ /** Configure APIClaw MCP server for this client */
47
+ configure(options?: ConfigureOptions): Promise<ConfigResult>;
48
+ /** Verify the current configuration */
49
+ verify(): Promise<VerifyResult>;
50
+ /** Get detailed installation info */
51
+ getInstallInfo(): Promise<InstallInfo>;
52
+ /** Remove APIClaw configuration */
53
+ unconfigure(serverName?: string): Promise<ConfigResult>;
54
+ }
55
+ /**
56
+ * Abstract base class for MCP client adapters
57
+ * Provides common functionality for all adapters
58
+ */
59
+ export declare abstract class BaseAdapter implements MCPClientAdapter {
60
+ abstract name: MCPClient | 'custom';
61
+ abstract displayName: string;
62
+ protected os: Platform;
63
+ constructor();
64
+ /**
65
+ * Get the config file path
66
+ */
67
+ getConfigPath(): string;
68
+ /**
69
+ * Check if config file exists
70
+ */
71
+ protected configExists(): boolean;
72
+ /**
73
+ * Get application paths to check for installation
74
+ * Override in subclasses for specific clients
75
+ */
76
+ protected abstract getAppPaths(): string[];
77
+ /**
78
+ * Check if the client application is installed
79
+ */
80
+ isInstalled(): Promise<boolean>;
81
+ /**
82
+ * Get detailed installation information
83
+ */
84
+ getInstallInfo(): Promise<InstallInfo>;
85
+ /**
86
+ * Configure APIClaw MCP server
87
+ */
88
+ configure(options?: ConfigureOptions): Promise<ConfigResult>;
89
+ /**
90
+ * Merge APIClaw config into existing config
91
+ * Override in subclasses for special formats (e.g., Continue)
92
+ */
93
+ protected mergeConfig(config: MCPConfig | ContinueConfig, options: {
94
+ workspace?: string;
95
+ serverName?: string;
96
+ force?: boolean;
97
+ }): MCPConfig | ContinueConfig;
98
+ /**
99
+ * Verify the current configuration
100
+ */
101
+ verify(): Promise<VerifyResult>;
102
+ /**
103
+ * Remove APIClaw configuration
104
+ */
105
+ unconfigure(serverName?: string): Promise<ConfigResult>;
106
+ /**
107
+ * Remove APIClaw from config
108
+ * Override in subclasses for special formats
109
+ */
110
+ protected removeFromConfig(config: MCPConfig | ContinueConfig, serverName: string): MCPConfig | ContinueConfig;
111
+ }
112
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":"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"}