@nordsym/apiclaw 1.8.6 → 1.8.7

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 (125) hide show
  1. package/README.md +4 -4
  2. package/apiclaw-README.md +12 -12
  3. package/convex/_generated/api.d.ts +4 -0
  4. package/convex/apiKeys.ts +220 -0
  5. package/convex/directCall.ts +49 -14
  6. package/convex/email.ts +5 -5
  7. package/convex/http.ts +598 -40
  8. package/convex/logs.ts +26 -22
  9. package/convex/migrateProviderWorkspaces.ts +154 -35
  10. package/convex/providers.ts +313 -203
  11. package/convex/schema.ts +50 -1
  12. package/convex/searchLogs.ts +2 -6
  13. package/convex/seedPratham.ts +1 -1
  14. package/convex/spendAlerts.ts +2 -2
  15. package/convex/stripeActions.ts +1 -1
  16. package/convex/updateAPIStatus.ts +2 -3
  17. package/convex/workspaceSettings.ts +136 -0
  18. package/dist/cli/commands/demo.js +2 -2
  19. package/dist/cli/commands/doctor.js +1 -1
  20. package/dist/cli/commands/login.js +1 -1
  21. package/dist/cli/commands/setup.js +2 -2
  22. package/dist/discovery.js +1 -1
  23. package/dist/execute.js +3 -3
  24. package/dist/index.js +13 -13
  25. package/dist/ui/errors.js +16 -16
  26. package/dist/ui/prompts.js +1 -1
  27. package/email-templates/filestack-provider-outreach.html +1 -1
  28. package/email-templates/partnership-template.html +1 -1
  29. package/email-templates/pratham-partnership-draft.html +2 -2
  30. package/package.json +2 -2
  31. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  32. package/reports/session-report-2026-04-05.html +433 -0
  33. package/src/cli/commands/demo.ts +2 -2
  34. package/src/cli/commands/doctor.ts +1 -1
  35. package/src/cli/commands/login.ts +1 -1
  36. package/src/cli/commands/setup.ts +2 -2
  37. package/src/discovery.ts +1 -1
  38. package/src/execute.ts +3 -3
  39. package/src/index.ts +14 -14
  40. package/src/ui/errors.ts +16 -16
  41. package/src/ui/prompts.ts +1 -1
  42. package/convex/adminActivate.d.ts +0 -3
  43. package/convex/adminActivate.js +0 -47
  44. package/convex/adminStats.d.ts +0 -9
  45. package/convex/adminStats.js +0 -280
  46. package/convex/agents.d.ts +0 -84
  47. package/convex/agents.js +0 -809
  48. package/convex/analytics.d.ts +0 -5
  49. package/convex/analytics.js +0 -167
  50. package/convex/backfillAnalytics.d.ts +0 -2
  51. package/convex/backfillAnalytics.js +0 -20
  52. package/convex/backfillSearchLogs.d.ts +0 -2
  53. package/convex/backfillSearchLogs.js +0 -29
  54. package/convex/billing.d.ts +0 -88
  55. package/convex/billing.js +0 -655
  56. package/convex/capabilities.d.ts +0 -9
  57. package/convex/capabilities.js +0 -145
  58. package/convex/chains.d.ts +0 -68
  59. package/convex/chains.js +0 -1105
  60. package/convex/credits.d.ts +0 -25
  61. package/convex/credits.js +0 -186
  62. package/convex/crons.d.ts +0 -3
  63. package/convex/crons.js +0 -17
  64. package/convex/debugFilestackLogs.d.ts +0 -2
  65. package/convex/debugFilestackLogs.js +0 -17
  66. package/convex/debugGetToken.d.ts +0 -2
  67. package/convex/debugGetToken.js +0 -18
  68. package/convex/directCall.d.ts +0 -72
  69. package/convex/directCall.js +0 -627
  70. package/convex/earnProgress.d.ts +0 -58
  71. package/convex/earnProgress.js +0 -649
  72. package/convex/email.d.ts +0 -14
  73. package/convex/email.js +0 -300
  74. package/convex/feedback.d.ts +0 -7
  75. package/convex/feedback.js +0 -227
  76. package/convex/http.d.ts +0 -3
  77. package/convex/http.js +0 -1408
  78. package/convex/inbound.d.ts +0 -2
  79. package/convex/inbound.js +0 -32
  80. package/convex/logs.d.ts +0 -48
  81. package/convex/logs.js +0 -621
  82. package/convex/migrateFilestack.d.ts +0 -2
  83. package/convex/migrateFilestack.js +0 -74
  84. package/convex/migratePartnersProd.d.ts +0 -8
  85. package/convex/migratePartnersProd.js +0 -165
  86. package/convex/migratePratham.d.ts +0 -2
  87. package/convex/migratePratham.js +0 -121
  88. package/convex/migrateProviderWorkspaces.d.ts +0 -2
  89. package/convex/migrateProviderWorkspaces.js +0 -55
  90. package/convex/mou.d.ts +0 -6
  91. package/convex/mou.js +0 -82
  92. package/convex/providerKeys.d.ts +0 -31
  93. package/convex/providerKeys.js +0 -257
  94. package/convex/providers.d.ts +0 -35
  95. package/convex/providers.js +0 -922
  96. package/convex/purchases.d.ts +0 -7
  97. package/convex/purchases.js +0 -157
  98. package/convex/ratelimit.d.ts +0 -4
  99. package/convex/ratelimit.js +0 -91
  100. package/convex/searchLogs.d.ts +0 -13
  101. package/convex/searchLogs.js +0 -246
  102. package/convex/seedAPILayerAPIs.d.ts +0 -7
  103. package/convex/seedAPILayerAPIs.js +0 -177
  104. package/convex/seedDirectCallConfigs.d.ts +0 -2
  105. package/convex/seedDirectCallConfigs.js +0 -324
  106. package/convex/seedPratham.d.ts +0 -6
  107. package/convex/seedPratham.js +0 -150
  108. package/convex/spendAlerts.d.ts +0 -36
  109. package/convex/spendAlerts.js +0 -380
  110. package/convex/stripeActions.d.ts +0 -19
  111. package/convex/stripeActions.js +0 -411
  112. package/convex/teams.d.ts +0 -21
  113. package/convex/teams.js +0 -215
  114. package/convex/telemetry.d.ts +0 -4
  115. package/convex/telemetry.js +0 -74
  116. package/convex/updateAPIStatus.d.ts +0 -6
  117. package/convex/updateAPIStatus.js +0 -40
  118. package/convex/usage.d.ts +0 -27
  119. package/convex/usage.js +0 -229
  120. package/convex/waitlist.d.ts +0 -4
  121. package/convex/waitlist.js +0 -49
  122. package/convex/webhooks.d.ts +0 -12
  123. package/convex/webhooks.js +0 -410
  124. package/convex/workspaces.d.ts +0 -33
  125. package/convex/workspaces.js +0 -991
package/convex/logs.ts CHANGED
@@ -91,19 +91,16 @@ export const logProviderCall = mutation({
91
91
  errorMessage: v.optional(v.string()),
92
92
  },
93
93
  handler: async (ctx, args) => {
94
- // Map provider to workspace email (for providers with workspaces)
95
- const providerEmailMap: Record<string, string> = {
96
- apilayer: "pratham@apilayer.com",
97
- };
98
-
99
- const providerEmail = providerEmailMap[args.provider];
100
- let workspace = null;
101
-
102
- if (providerEmail) {
103
- workspace = await ctx.db
104
- .query("workspaces")
105
- .withIndex("by_email", (q) => q.eq("email", providerEmail))
106
- .first();
94
+ // Resolve provider workspace dynamically (no hardcoded email maps)
95
+ const providerNameLower = args.provider.toLowerCase();
96
+ const allProviders = await ctx.db.query("providers").collect();
97
+ const providerRecord = allProviders.find(
98
+ (p) => p.name.toLowerCase() === providerNameLower
99
+ );
100
+
101
+ let workspace: any = null;
102
+ if (providerRecord && (providerRecord as any).workspaceId) {
103
+ workspace = await ctx.db.get((providerRecord as any).workspaceId);
107
104
  }
108
105
 
109
106
  // Always log to global provider analytics (even without workspace)
@@ -148,8 +145,9 @@ export const getProviderAnalytics = query({
148
145
  args: {
149
146
  token: v.string(),
150
147
  hoursBack: v.optional(v.number()),
148
+ direction: v.optional(v.string()), // "outbound" = my usage, "inbound" = traffic to my APIs, omit = all
151
149
  },
152
- handler: async (ctx, { token, hoursBack = 168 }) => {
150
+ handler: async (ctx, { token, hoursBack = 168, direction }) => {
153
151
  const session = await ctx.db
154
152
  .query("agentSessions")
155
153
  .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
@@ -164,11 +162,18 @@ export const getProviderAnalytics = query({
164
162
  .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
165
163
  .collect();
166
164
 
167
- const periodLogs = allLogs.filter((l) => l.createdAt >= since);
165
+ let periodLogs = allLogs.filter((l) => l.createdAt >= since);
166
+
167
+ // Filter by direction if specified
168
+ if (direction === "outbound") {
169
+ periodLogs = periodLogs.filter((l) => l.direction !== "inbound");
170
+ } else if (direction === "inbound") {
171
+ periodLogs = periodLogs.filter((l) => l.direction === "inbound");
172
+ }
173
+
168
174
  const inboundLogs = periodLogs.filter((l) => l.direction === "inbound");
169
- const outboundLogs = periodLogs.filter((l) => l.direction !== "inbound");
170
175
 
171
- // Daily buckets — separate calls and searches
176
+ // Daily buckets
172
177
  const byDay: Record<string, { calls: number; searches: number }> = {};
173
178
  periodLogs.forEach((l) => {
174
179
  const day = new Date(l.createdAt).toISOString().split("T")[0];
@@ -180,11 +185,10 @@ export const getProviderAnalytics = query({
180
185
  }
181
186
  });
182
187
 
183
- // By action (individual API endpoints, not just provider name)
188
+ // By action
184
189
  const byAction: Record<string, { calls: number; success: number; type: string }> = {};
185
190
  periodLogs.forEach((l) => {
186
191
  const isDiscovery = l.action.startsWith("discovery:");
187
- const key = isDiscovery ? l.action : `${l.provider}:${l.action}`;
188
192
  const displayName = isDiscovery ? l.action.replace("discovery:", "Search: ") : l.action;
189
193
  if (!byAction[displayName]) byAction[displayName] = { calls: 0, success: 0, type: isDiscovery ? "discovery" : "call" };
190
194
  byAction[displayName].calls++;
@@ -194,7 +198,6 @@ export const getProviderAnalytics = query({
194
198
  // Unique callers (for inbound)
195
199
  const uniqueCallers = new Set(inboundLogs.map((l) => l.callerWorkspaceId).filter(Boolean)).size;
196
200
 
197
- // Counts
198
201
  const callLogs = periodLogs.filter((l) => !l.action.startsWith("discovery:"));
199
202
  const discoveryLogs = periodLogs.filter((l) => l.action.startsWith("discovery:"));
200
203
 
@@ -202,7 +205,7 @@ export const getProviderAnalytics = query({
202
205
  totalCalls: callLogs.length,
203
206
  totalDiscoveries: discoveryLogs.length,
204
207
  inboundCalls: inboundLogs.filter((l) => !l.action.startsWith("discovery:")).length,
205
- outboundCalls: outboundLogs.length,
208
+ uniqueCallers,
206
209
  byDay: Object.entries(byDay)
207
210
  .map(([date, data]) => ({ date, calls: data.calls, searches: data.searches }))
208
211
  .sort((a, b) => a.date.localeCompare(b.date)),
@@ -668,7 +671,8 @@ export const createProxyLog = mutation({
668
671
  subagentId: subagentId || "unknown",
669
672
  sessionToken: sessionToken || "proxy",
670
673
  status: "success",
671
- latencyMs: 0, // Proxy calls don't track latency
674
+ latencyMs: 0,
675
+ direction: "outbound",
672
676
  createdAt: now,
673
677
  });
674
678
 
@@ -1,56 +1,175 @@
1
1
  import { mutation } from "./_generated/server";
2
- import { v } from "convex/values";
3
2
 
4
- // Backfill workspaceId on providerAPIs rows that have providerId but no workspaceId
3
+ /**
4
+ * Workspace Unification Migration (idempotent)
5
+ *
6
+ * For every providers record, find the matching workspace by email
7
+ * and backfill workspaceId on the provider + all related tables.
8
+ *
9
+ * Does NOT create new workspaces. If no workspace is found for a
10
+ * provider email, it is logged in the report for manual handling.
11
+ *
12
+ * Safe to run multiple times — skips already-linked records.
13
+ */
5
14
  export const run = mutation({
6
15
  args: {},
7
16
  handler: async (ctx) => {
8
- const results: Record<string, number> = {};
9
-
10
- const pairs = [
11
- {
12
- providerEmail: "pratham.kumar@apilayer.com",
13
- workspaceEmail: "pratham.kumar@apilayer.com",
14
- label: "APILayer",
15
- },
16
- {
17
- providerEmail: "marketing@filestack.com",
18
- workspaceEmail: "marketing@filestack.com",
19
- label: "Filestack",
20
- },
21
- ];
22
-
23
- for (const pair of pairs) {
24
- // Get provider
25
- const provider = await ctx.db
26
- .query("providers")
27
- .withIndex("by_email", (q) => q.eq("email", pair.providerEmail))
28
- .first();
29
- if (!provider) { results[pair.label] = -1; continue; }
17
+ const report: {
18
+ providers: Array<{
19
+ email: string;
20
+ name: string;
21
+ providerId: string;
22
+ workspaceId: string | null;
23
+ status: "linked" | "already_linked" | "workspace_not_found";
24
+ backfilled: {
25
+ providerAPIs: number;
26
+ apis: number;
27
+ apiCalls: number;
28
+ payouts: number;
29
+ providerDirectCall: number;
30
+ usageLog: number;
31
+ };
32
+ }>;
33
+ warnings: string[];
34
+ } = { providers: [], warnings: [] };
35
+
36
+ // 1. Get ALL provider records (no hardcoded list)
37
+ const allProviders = await ctx.db.query("providers").collect();
38
+
39
+ for (const provider of allProviders) {
40
+ const entry: (typeof report.providers)[number] = {
41
+ email: provider.email,
42
+ name: provider.name,
43
+ providerId: provider._id as string,
44
+ workspaceId: null,
45
+ status: "workspace_not_found",
46
+ backfilled: {
47
+ providerAPIs: 0,
48
+ apis: 0,
49
+ apiCalls: 0,
50
+ payouts: 0,
51
+ providerDirectCall: 0,
52
+ usageLog: 0,
53
+ },
54
+ };
30
55
 
31
- // Get workspace
56
+ // Already linked?
57
+ if (provider.workspaceId) {
58
+ entry.workspaceId = provider.workspaceId as string;
59
+ entry.status = "already_linked";
60
+ // Still backfill child tables in case they were missed
61
+ }
62
+
63
+ // 2. Find workspace by email (case-insensitive)
64
+ const emailLower = provider.email.toLowerCase();
32
65
  const workspace = await ctx.db
33
66
  .query("workspaces")
34
- .withIndex("by_email", (q) => q.eq("email", pair.workspaceEmail))
67
+ .withIndex("by_email", (q) => q.eq("email", emailLower))
35
68
  .first();
36
- if (!workspace) { results[pair.label] = -2; continue; }
37
69
 
38
- // Get all providerAPIs for this provider
39
- const apis = await ctx.db
70
+ if (!workspace) {
71
+ // Try exact match as fallback (in case stored with different casing)
72
+ const workspaceExact = await ctx.db
73
+ .query("workspaces")
74
+ .withIndex("by_email", (q) => q.eq("email", provider.email))
75
+ .first();
76
+
77
+ if (!workspaceExact) {
78
+ report.warnings.push(
79
+ `No workspace found for provider "${provider.name}" (${provider.email})`
80
+ );
81
+ report.providers.push(entry);
82
+ continue;
83
+ }
84
+ // Use exact match
85
+ entry.workspaceId = workspaceExact._id as string;
86
+ } else {
87
+ entry.workspaceId = workspace._id as string;
88
+ }
89
+
90
+ const wsId = entry.workspaceId! as any;
91
+
92
+ // 3. Link provider → workspace (if not already)
93
+ if (!provider.workspaceId) {
94
+ await ctx.db.patch(provider._id, { workspaceId: wsId });
95
+ entry.status = "linked";
96
+ }
97
+
98
+ // 4. Backfill providerAPIs (skip if workspaceId already set)
99
+ const providerAPIs = await ctx.db
40
100
  .query("providerAPIs")
41
101
  .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
42
102
  .collect();
103
+ for (const api of providerAPIs) {
104
+ if (!api.workspaceId) {
105
+ await ctx.db.patch(api._id, { workspaceId: wsId });
106
+ entry.backfilled.providerAPIs++;
107
+ }
108
+ }
43
109
 
44
- let patched = 0;
110
+ // 5. Backfill apis
111
+ const apis = await ctx.db
112
+ .query("apis")
113
+ .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
114
+ .collect();
45
115
  for (const api of apis) {
46
- if (!api.workspaceId) {
47
- await ctx.db.patch(api._id, { workspaceId: workspace._id });
48
- patched++;
116
+ if (!(api as any).workspaceId) {
117
+ await ctx.db.patch(api._id, { workspaceId: wsId } as any);
118
+ entry.backfilled.apis++;
49
119
  }
50
120
  }
51
- results[pair.label] = patched;
121
+
122
+ // 6. Backfill apiCalls
123
+ const apiCalls = await ctx.db
124
+ .query("apiCalls")
125
+ .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
126
+ .collect();
127
+ for (const call of apiCalls) {
128
+ if (!(call as any).workspaceId) {
129
+ await ctx.db.patch(call._id, { workspaceId: wsId } as any);
130
+ entry.backfilled.apiCalls++;
131
+ }
132
+ }
133
+
134
+ // 7. Backfill payouts
135
+ const payouts = await ctx.db
136
+ .query("payouts")
137
+ .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
138
+ .collect();
139
+ for (const payout of payouts) {
140
+ if (!(payout as any).workspaceId) {
141
+ await ctx.db.patch(payout._id, { workspaceId: wsId } as any);
142
+ entry.backfilled.payouts++;
143
+ }
144
+ }
145
+
146
+ // 8. Backfill providerDirectCall
147
+ const directCalls = await ctx.db
148
+ .query("providerDirectCall")
149
+ .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
150
+ .collect();
151
+ for (const dc of directCalls) {
152
+ if (!(dc as any).workspaceId) {
153
+ await ctx.db.patch(dc._id, { workspaceId: wsId } as any);
154
+ entry.backfilled.providerDirectCall++;
155
+ }
156
+ }
157
+
158
+ // 9. Backfill usageLog
159
+ const usageLogs = await ctx.db
160
+ .query("usageLog")
161
+ .withIndex("by_providerId", (q) => q.eq("providerId", provider._id))
162
+ .collect();
163
+ for (const log of usageLogs) {
164
+ if (!(log as any).workspaceId) {
165
+ await ctx.db.patch(log._id, { workspaceId: wsId } as any);
166
+ entry.backfilled.usageLog++;
167
+ }
168
+ }
169
+
170
+ report.providers.push(entry);
52
171
  }
53
172
 
54
- return results;
173
+ return report;
55
174
  },
56
175
  });