@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
@@ -8,12 +8,14 @@
8
8
  * @module
9
9
  */
10
10
 
11
+ import type * as agents from "../agents.js";
11
12
  import type * as analytics from "../analytics.js";
12
13
  import type * as billing from "../billing.js";
13
14
  import type * as capabilities from "../capabilities.js";
14
15
  import type * as credits from "../credits.js";
15
16
  import type * as crons from "../crons.js";
16
17
  import type * as directCall from "../directCall.js";
18
+ import type * as earnProgress from "../earnProgress.js";
17
19
  import type * as email from "../email.js";
18
20
  import type * as feedback from "../feedback.js";
19
21
  import type * as http from "../http.js";
@@ -36,12 +38,14 @@ import type {
36
38
  } from "convex/server";
37
39
 
38
40
  declare const fullApi: ApiFromModules<{
41
+ agents: typeof agents;
39
42
  analytics: typeof analytics;
40
43
  billing: typeof billing;
41
44
  capabilities: typeof capabilities;
42
45
  credits: typeof credits;
43
46
  crons: typeof crons;
44
47
  directCall: typeof directCall;
48
+ earnProgress: typeof earnProgress;
45
49
  email: typeof email;
46
50
  feedback: typeof feedback;
47
51
  http: typeof http;
@@ -0,0 +1,403 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query, internalMutation } from "./_generated/server";
3
+
4
+ // ============================================
5
+ // AGENT NAME GENERATION
6
+ // ============================================
7
+
8
+ const ADJECTIVES = [
9
+ "Crimson", "Azure", "Golden", "Silver", "Obsidian",
10
+ "Emerald", "Sapphire", "Violet", "Amber", "Jade",
11
+ "Scarlet", "Cobalt", "Onyx", "Ruby", "Pearl",
12
+ "Shadow", "Storm", "Frost", "Blaze", "Thunder",
13
+ "Swift", "Silent", "Bright", "Dark", "Wild",
14
+ "Noble", "Fierce", "Cosmic", "Quantum", "Neural",
15
+ ];
16
+
17
+ const NOUNS = [
18
+ "Phoenix", "Falcon", "Dragon", "Wolf", "Raven",
19
+ "Serpent", "Tiger", "Hawk", "Panther", "Lynx",
20
+ "Cipher", "Vector", "Prism", "Nexus", "Core",
21
+ "Agent", "Oracle", "Sentinel", "Phantom", "Vanguard",
22
+ "Forge", "Spark", "Pulse", "Echo", "Byte",
23
+ "Matrix", "Vertex", "Helix", "Nova", "Zenith",
24
+ ];
25
+
26
+ function generateAgentName(): string {
27
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
28
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
29
+ return `${adj} ${noun}`;
30
+ }
31
+
32
+ function generateUUID(): string {
33
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
34
+ const r = Math.random() * 16 | 0;
35
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
36
+ return v.toString(16);
37
+ });
38
+ }
39
+
40
+ // ============================================
41
+ // MAIN AGENT QUERIES
42
+ // ============================================
43
+
44
+ /**
45
+ * Get main agent info for a workspace
46
+ */
47
+ export const getMainAgent = query({
48
+ args: { token: v.string() },
49
+ handler: async (ctx, { token }) => {
50
+ const session = await ctx.db
51
+ .query("agentSessions")
52
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
53
+ .first();
54
+
55
+ if (!session) {
56
+ return null;
57
+ }
58
+
59
+ const workspace = await ctx.db.get(session.workspaceId);
60
+ if (!workspace) {
61
+ return null;
62
+ }
63
+
64
+ return {
65
+ workspaceId: workspace._id,
66
+ email: workspace.email,
67
+ mainAgentId: workspace.mainAgentId || null,
68
+ mainAgentName: workspace.mainAgentName || null,
69
+ usageCount: workspace.usageCount,
70
+ createdAt: workspace.createdAt,
71
+ };
72
+ },
73
+ });
74
+
75
+ /**
76
+ * Rename the main agent
77
+ */
78
+ export const renameMainAgent = mutation({
79
+ args: {
80
+ token: v.string(),
81
+ name: v.string(),
82
+ },
83
+ handler: async (ctx, { token, name }) => {
84
+ const session = await ctx.db
85
+ .query("agentSessions")
86
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
87
+ .first();
88
+
89
+ if (!session) {
90
+ throw new Error("Invalid session");
91
+ }
92
+
93
+ const workspace = await ctx.db.get(session.workspaceId);
94
+ if (!workspace) {
95
+ throw new Error("Workspace not found");
96
+ }
97
+
98
+ // Validate name length
99
+ const trimmedName = name.trim();
100
+ if (trimmedName.length < 2 || trimmedName.length > 50) {
101
+ throw new Error("Name must be between 2 and 50 characters");
102
+ }
103
+
104
+ await ctx.db.patch(workspace._id, {
105
+ mainAgentName: trimmedName,
106
+ updatedAt: Date.now(),
107
+ });
108
+
109
+ return { success: true, name: trimmedName };
110
+ },
111
+ });
112
+
113
+ /**
114
+ * Initialize main agent (auto-generate name and ID if not set)
115
+ * Called on first API call
116
+ */
117
+ export const ensureMainAgent = mutation({
118
+ args: { workspaceId: v.id("workspaces") },
119
+ handler: async (ctx, { workspaceId }) => {
120
+ const workspace = await ctx.db.get(workspaceId);
121
+ if (!workspace) {
122
+ throw new Error("Workspace not found");
123
+ }
124
+
125
+ // Already initialized
126
+ if (workspace.mainAgentId && workspace.mainAgentName) {
127
+ return {
128
+ mainAgentId: workspace.mainAgentId,
129
+ mainAgentName: workspace.mainAgentName,
130
+ created: false,
131
+ };
132
+ }
133
+
134
+ const mainAgentId = workspace.mainAgentId || generateUUID();
135
+ const mainAgentName = workspace.mainAgentName || generateAgentName();
136
+
137
+ await ctx.db.patch(workspaceId, {
138
+ mainAgentId,
139
+ mainAgentName,
140
+ updatedAt: Date.now(),
141
+ });
142
+
143
+ return {
144
+ mainAgentId,
145
+ mainAgentName,
146
+ created: true,
147
+ };
148
+ },
149
+ });
150
+
151
+ // ============================================
152
+ // SUBAGENT QUERIES
153
+ // ============================================
154
+
155
+ /**
156
+ * Get all subagents for a workspace
157
+ */
158
+ export const getSubagents = query({
159
+ args: {
160
+ token: v.string(),
161
+ limit: v.optional(v.number()),
162
+ },
163
+ handler: async (ctx, { token, limit = 50 }) => {
164
+ const session = await ctx.db
165
+ .query("agentSessions")
166
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
167
+ .first();
168
+
169
+ if (!session) {
170
+ return { subagents: [], total: 0 };
171
+ }
172
+
173
+ const subagents = await ctx.db
174
+ .query("subagents")
175
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
176
+ .order("desc")
177
+ .take(limit);
178
+
179
+ // Sort by lastActiveAt descending
180
+ const sorted = subagents.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
181
+
182
+ return {
183
+ subagents: sorted.map((s) => ({
184
+ id: s._id,
185
+ subagentId: s.subagentId,
186
+ name: s.name || s.subagentId,
187
+ callCount: s.callCount,
188
+ firstSeenAt: s.firstSeenAt,
189
+ lastActiveAt: s.lastActiveAt,
190
+ })),
191
+ total: subagents.length,
192
+ };
193
+ },
194
+ });
195
+
196
+ /**
197
+ * Get stats for a specific subagent
198
+ */
199
+ export const getSubagentStats = query({
200
+ args: {
201
+ token: v.string(),
202
+ subagentId: v.string(),
203
+ },
204
+ handler: async (ctx, { token, subagentId }) => {
205
+ const session = await ctx.db
206
+ .query("agentSessions")
207
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
208
+ .first();
209
+
210
+ if (!session) {
211
+ return null;
212
+ }
213
+
214
+ const subagent = await ctx.db
215
+ .query("subagents")
216
+ .withIndex("by_workspaceId_subagentId", (q) =>
217
+ q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId)
218
+ )
219
+ .first();
220
+
221
+ if (!subagent) {
222
+ return null;
223
+ }
224
+
225
+ // Get recent logs for this subagent
226
+ const logs = await ctx.db
227
+ .query("apiLogs")
228
+ .withIndex("by_subagentId", (q) => q.eq("subagentId", subagentId))
229
+ .order("desc")
230
+ .take(100);
231
+
232
+ const successCount = logs.filter((l) => l.status === "success").length;
233
+ const errorCount = logs.filter((l) => l.status === "error").length;
234
+ const avgLatency = logs.length > 0
235
+ ? Math.round(logs.reduce((sum, l) => sum + l.latencyMs, 0) / logs.length)
236
+ : 0;
237
+
238
+ // Group by provider
239
+ const byProvider: Record<string, number> = {};
240
+ for (const log of logs) {
241
+ byProvider[log.provider] = (byProvider[log.provider] || 0) + 1;
242
+ }
243
+
244
+ return {
245
+ subagentId: subagent.subagentId,
246
+ name: subagent.name || subagent.subagentId,
247
+ callCount: subagent.callCount,
248
+ successCount,
249
+ errorCount,
250
+ successRate: logs.length > 0 ? Math.round((successCount / logs.length) * 100) : 0,
251
+ avgLatency,
252
+ firstSeenAt: subagent.firstSeenAt,
253
+ lastActiveAt: subagent.lastActiveAt,
254
+ byProvider: Object.entries(byProvider)
255
+ .map(([provider, count]) => ({ provider, count }))
256
+ .sort((a, b) => b.count - a.count),
257
+ };
258
+ },
259
+ });
260
+
261
+ /**
262
+ * Rename a subagent
263
+ */
264
+ export const renameSubagent = mutation({
265
+ args: {
266
+ token: v.string(),
267
+ subagentId: v.string(),
268
+ name: v.string(),
269
+ },
270
+ handler: async (ctx, { token, subagentId, name }) => {
271
+ const session = await ctx.db
272
+ .query("agentSessions")
273
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
274
+ .first();
275
+
276
+ if (!session) {
277
+ throw new Error("Invalid session");
278
+ }
279
+
280
+ const subagent = await ctx.db
281
+ .query("subagents")
282
+ .withIndex("by_workspaceId_subagentId", (q) =>
283
+ q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId)
284
+ )
285
+ .first();
286
+
287
+ if (!subagent) {
288
+ throw new Error("Subagent not found");
289
+ }
290
+
291
+ const trimmedName = name.trim();
292
+ if (trimmedName.length < 1 || trimmedName.length > 100) {
293
+ throw new Error("Name must be between 1 and 100 characters");
294
+ }
295
+
296
+ await ctx.db.patch(subagent._id, { name: trimmedName });
297
+
298
+ return { success: true, name: trimmedName };
299
+ },
300
+ });
301
+
302
+ /**
303
+ * Track a subagent call (upsert subagent record)
304
+ * Called when X-APIClaw-Subagent header is present
305
+ */
306
+ export const trackSubagentCall = mutation({
307
+ args: {
308
+ workspaceId: v.id("workspaces"),
309
+ subagentId: v.string(),
310
+ },
311
+ handler: async (ctx, { workspaceId, subagentId }) => {
312
+ const now = Date.now();
313
+
314
+ // Find existing subagent record
315
+ const existing = await ctx.db
316
+ .query("subagents")
317
+ .withIndex("by_workspaceId_subagentId", (q) =>
318
+ q.eq("workspaceId", workspaceId).eq("subagentId", subagentId)
319
+ )
320
+ .first();
321
+
322
+ if (existing) {
323
+ // Increment call count
324
+ await ctx.db.patch(existing._id, {
325
+ callCount: existing.callCount + 1,
326
+ lastActiveAt: now,
327
+ });
328
+ return { id: existing._id, created: false };
329
+ }
330
+
331
+ // Create new subagent record
332
+ const id = await ctx.db.insert("subagents", {
333
+ workspaceId,
334
+ subagentId,
335
+ callCount: 1,
336
+ firstSeenAt: now,
337
+ lastActiveAt: now,
338
+ });
339
+
340
+ return { id, created: true };
341
+ },
342
+ });
343
+
344
+ // ============================================
345
+ // AGGREGATE STATS
346
+ // ============================================
347
+
348
+ /**
349
+ * Get agent overview for workspace (main + subagents summary)
350
+ */
351
+ export const getAgentOverview = query({
352
+ args: { token: v.string() },
353
+ handler: async (ctx, { token }) => {
354
+ const session = await ctx.db
355
+ .query("agentSessions")
356
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
357
+ .first();
358
+
359
+ if (!session) {
360
+ return null;
361
+ }
362
+
363
+ const workspace = await ctx.db.get(session.workspaceId);
364
+ if (!workspace) {
365
+ return null;
366
+ }
367
+
368
+ // Get all subagents
369
+ const subagents = await ctx.db
370
+ .query("subagents")
371
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
372
+ .collect();
373
+
374
+ // Calculate totals
375
+ const totalSubagentCalls = subagents.reduce((sum, s) => sum + s.callCount, 0);
376
+ const mainAgentCalls = workspace.usageCount - totalSubagentCalls;
377
+
378
+ // Get most active subagents (top 5)
379
+ const topSubagents = subagents
380
+ .sort((a, b) => b.callCount - a.callCount)
381
+ .slice(0, 5)
382
+ .map((s) => ({
383
+ subagentId: s.subagentId,
384
+ name: s.name || s.subagentId,
385
+ callCount: s.callCount,
386
+ lastActiveAt: s.lastActiveAt,
387
+ }));
388
+
389
+ return {
390
+ mainAgent: {
391
+ id: workspace.mainAgentId || null,
392
+ name: workspace.mainAgentName || "Unnamed Agent",
393
+ callCount: Math.max(0, mainAgentCalls), // Ensure non-negative
394
+ },
395
+ subagents: {
396
+ total: subagents.length,
397
+ totalCalls: totalSubagentCalls,
398
+ topActive: topSubagents,
399
+ },
400
+ totalCalls: workspace.usageCount,
401
+ };
402
+ },
403
+ });
@@ -224,6 +224,7 @@ export const deleteAction = mutation({
224
224
 
225
225
  /**
226
226
  * Publish Direct Call - set status to live
227
+ * Also marks apiListed earn progress for the provider's workspace
227
228
  */
228
229
  export const publishDirectCall = mutation({
229
230
  args: {
@@ -231,15 +232,94 @@ export const publishDirectCall = mutation({
231
232
  },
232
233
  handler: async (ctx, args) => {
233
234
  const now = Date.now();
235
+
236
+ // Get the direct call config to find the provider
237
+ const config = await ctx.db.get(args.id);
238
+ if (!config) {
239
+ throw new Error("Direct Call config not found");
240
+ }
241
+
234
242
  await ctx.db.patch(args.id, {
235
243
  status: "live",
236
244
  publishedAt: now,
237
245
  updatedAt: now,
238
246
  });
247
+
248
+ // Try to mark apiListed for earn progress
249
+ // First, get the provider to find their session/workspace
250
+ const provider = await ctx.db.get(config.providerId);
251
+ if (provider) {
252
+ // Find workspace by provider email
253
+ const workspace = await ctx.db
254
+ .query("workspaces")
255
+ .withIndex("by_email", (q) => q.eq("email", provider.email))
256
+ .first();
257
+
258
+ if (workspace) {
259
+ // Update earn progress
260
+ const earnProgress = await ctx.db
261
+ .query("earnProgress")
262
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspace._id))
263
+ .first();
264
+
265
+ if (earnProgress && !earnProgress.apiListed) {
266
+ const newTotal = calculateEarnTotal({ ...earnProgress, apiListed: true });
267
+ await ctx.db.patch(earnProgress._id, {
268
+ apiListed: true,
269
+ apiListedAt: now,
270
+ totalEarned: newTotal,
271
+ updatedAt: now,
272
+ });
273
+ // Add 10 calls to workspace limit
274
+ await ctx.db.patch(workspace._id, {
275
+ usageLimit: workspace.usageLimit + 10,
276
+ updatedAt: now,
277
+ });
278
+ } else if (!earnProgress) {
279
+ // Create earn progress with apiListed
280
+ await ctx.db.insert("earnProgress", {
281
+ workspaceId: workspace._id,
282
+ firstDirectCall: false,
283
+ apisUsed: [],
284
+ apisUsedComplete: false,
285
+ agentListed: false,
286
+ apiListed: true,
287
+ apiListedAt: now,
288
+ byokSetup: false,
289
+ githubStarred: false,
290
+ twitterFollowed: false,
291
+ referralCount: 0,
292
+ totalEarned: 10,
293
+ createdAt: now,
294
+ updatedAt: now,
295
+ });
296
+ // Add 10 calls to workspace limit
297
+ await ctx.db.patch(workspace._id, {
298
+ usageLimit: workspace.usageLimit + 10,
299
+ updatedAt: now,
300
+ });
301
+ }
302
+ }
303
+ }
304
+
239
305
  return { success: true, publishedAt: now };
240
306
  },
241
307
  });
242
308
 
309
+ // Helper to calculate earn total (duplicated to avoid circular import)
310
+ function calculateEarnTotal(progress: any): number {
311
+ let total = 0;
312
+ if (progress.firstDirectCall) total += 15;
313
+ if (progress.apisUsedComplete) total += 10;
314
+ if (progress.agentListed) total += 10;
315
+ if (progress.apiListed) total += 10;
316
+ if (progress.byokSetup) total += 5;
317
+ if (progress.githubStarred) total += 10;
318
+ if (progress.twitterFollowed) total += 5;
319
+ total += (progress.referralCount || 0) * 10;
320
+ return total;
321
+ }
322
+
243
323
  /**
244
324
  * Set Direct Call status (draft, testing, live)
245
325
  */