@nordsym/apiclaw 1.5.5 → 1.5.6

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.
@@ -0,0 +1,29 @@
1
+ # APIClaw Dashboard Fix Plan
2
+
3
+ ## Issues Found:
4
+
5
+ ### 1. Agent Count Mismatch (8 vs 1)
6
+ **Root cause:** Overview counts `agentSessions` (stale), My Agents tab queries `agents` table (correct)
7
+ **Fix:** Update `getWorkspaceDashboard` to count from `agents` table instead
8
+
9
+ ### 2. Analytics = Preview Mode
10
+ **Root cause:** `analytics:getProviderBreakdown` doesn't exist
11
+ **Fix:** Create query that aggregates from `apiLogs` table
12
+
13
+ ### 3. Usage Count (claims 7, actually 1)
14
+ **Diagnosis:** Backend correctly shows 1, dashboard may be cached
15
+ **Fix:** Hard refresh should resolve, but verify incrementUsage is called
16
+
17
+ ### 4. lastActiveAt Frozen
18
+ **Diagnosis:** Not being updated on proxy calls
19
+ **Fix:** Update agent.lastActiveAt when logging proxy call
20
+
21
+ ## Implementation Order:
22
+
23
+ 1. Create `analytics:getProviderBreakdown` query
24
+ 2. Fix `getWorkspaceDashboard` to count agents correctly
25
+ 3. Update `createProxyLog` to touch agent lastActiveAt
26
+ 4. Remove preview fallback in frontend
27
+ 5. Rename "Direct Call" → "API Catalog"
28
+
29
+ Time estimate: 30 mins
@@ -88,3 +88,99 @@ export const getRecent = query({
88
88
  .take(limit);
89
89
  },
90
90
  });
91
+
92
+ // Get provider breakdown for Agent Analytics (workspace-specific)
93
+ export const getProviderBreakdown = query({
94
+ args: {
95
+ token: v.string(),
96
+ periodDays: v.optional(v.number()),
97
+ },
98
+ handler: async (ctx, args) => {
99
+ const periodDays = args.periodDays || 7;
100
+ const since = Date.now() - periodDays * 24 * 3600000;
101
+
102
+ // Verify session and get workspace
103
+ const session = await ctx.db
104
+ .query("agentSessions")
105
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
106
+ .first();
107
+
108
+ if (!session) {
109
+ return null;
110
+ }
111
+
112
+ // Get all API logs for this workspace
113
+ const logs = await ctx.db
114
+ .query("apiLogs")
115
+ .withIndex("by_workspaceId_createdAt", (q) => q.eq("workspaceId", session.workspaceId))
116
+ .filter((q) => q.gte(q.field("createdAt"), since))
117
+ .collect();
118
+
119
+ if (logs.length === 0) {
120
+ return null; // Return null so frontend knows to show empty state, not preview
121
+ }
122
+
123
+ // Aggregate stats
124
+ const totalCalls = logs.length;
125
+ const successCount = logs.filter((l) => l.status === "success").length;
126
+ const failureCount = logs.filter((l) => l.status === "error").length;
127
+ const totalLatency = logs.reduce((sum, l) => sum + (l.latencyMs || 0), 0);
128
+ const avgLatency = totalCalls > 0 ? Math.round(totalLatency / totalCalls) : 0;
129
+
130
+ // Provider breakdown
131
+ const byProvider: Record<string, { count: number; latency: number }> = {};
132
+ for (const log of logs) {
133
+ if (!byProvider[log.provider]) {
134
+ byProvider[log.provider] = { count: 0, latency: 0 };
135
+ }
136
+ byProvider[log.provider].count++;
137
+ byProvider[log.provider].latency += log.latencyMs || 0;
138
+ }
139
+
140
+ // Agent breakdown (by subagentId)
141
+ const byAgent: Record<string, number> = {};
142
+ for (const log of logs) {
143
+ const agent = log.subagentId || "main";
144
+ byAgent[agent] = (byAgent[agent] || 0) + 1;
145
+ }
146
+
147
+ // Action breakdown
148
+ const byAction: Record<string, number> = {};
149
+ for (const log of logs) {
150
+ const key = `${log.provider}:${log.action}`;
151
+ byAction[key] = (byAction[key] || 0) + 1;
152
+ }
153
+
154
+ // Time series (daily)
155
+ const dailyCounts: Record<string, number> = {};
156
+ for (const log of logs) {
157
+ const day = new Date(log.createdAt).toISOString().slice(0, 10);
158
+ dailyCounts[day] = (dailyCounts[day] || 0) + 1;
159
+ }
160
+
161
+ return {
162
+ totalCalls,
163
+ successCount,
164
+ failureCount,
165
+ successRate: totalCalls > 0 ? (successCount / totalCalls) * 100 : 0,
166
+ avgLatency,
167
+ byProvider: Object.entries(byProvider).map(([name, data]) => ({
168
+ name,
169
+ count: data.count,
170
+ avgLatency: data.count > 0 ? Math.round(data.latency / data.count) : 0,
171
+ })).sort((a, b) => b.count - a.count),
172
+ byAgent: Object.entries(byAgent).map(([name, count]) => ({
173
+ name,
174
+ count,
175
+ })).sort((a, b) => b.count - a.count),
176
+ byAction: Object.entries(byAction).map(([name, count]) => ({
177
+ name,
178
+ count,
179
+ })).sort((a, b) => b.count - a.count).slice(0, 10),
180
+ timeSeries: Object.entries(dailyCounts)
181
+ .sort(([a], [b]) => a.localeCompare(b))
182
+ .map(([date, count]) => ({ date, count })),
183
+ isPreview: false,
184
+ };
185
+ },
186
+ });
package/convex/logs.ts CHANGED
@@ -514,6 +514,8 @@ export const createProxyLog = mutation({
514
514
  sessionToken: v.optional(v.string()),
515
515
  },
516
516
  handler: async (ctx, { workspaceId, provider, action, subagentId, sessionToken }) => {
517
+ const now = Date.now();
518
+
517
519
  await ctx.db.insert("apiLogs", {
518
520
  workspaceId,
519
521
  provider,
@@ -522,9 +524,22 @@ export const createProxyLog = mutation({
522
524
  sessionToken: sessionToken || "proxy",
523
525
  status: "success",
524
526
  latencyMs: 0, // Proxy calls don't track latency
525
- createdAt: Date.now(),
527
+ createdAt: now,
526
528
  });
527
529
 
530
+ // If this is a subagent call, update that subagent's timestamp
531
+ if (subagentId && subagentId !== "unknown" && subagentId !== "main") {
532
+ const subagent = await ctx.db
533
+ .query("subagents")
534
+ .withIndex("by_workspaceId_subagentId", (q) =>
535
+ q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
536
+ .first();
537
+
538
+ if (subagent) {
539
+ await ctx.db.patch(subagent._id, { lastActiveAt: now });
540
+ }
541
+ }
542
+
528
543
  return { success: true };
529
544
  },
530
545
  });
@@ -226,6 +226,15 @@ export const getWorkspaceDashboard = query({
226
226
  .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
227
227
  .collect();
228
228
 
229
+ // Count agents: 1 main agent (if exists) + subagents
230
+ const hasMainAgent = workspace.mainAgentId ? 1 : 0;
231
+ const subagents = await ctx.db
232
+ .query("subagents")
233
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
234
+ .collect();
235
+
236
+ const totalAgentCount = hasMainAgent + subagents.length;
237
+
229
238
  // Get usage logs for this workspace (via agent credits or purchases)
230
239
  const credits = await ctx.db
231
240
  .query("agentCredits")
@@ -271,7 +280,7 @@ export const getWorkspaceDashboard = query({
271
280
  createdAt: workspace.createdAt,
272
281
  },
273
282
  stats: {
274
- totalAgents: agentSessions.length,
283
+ totalAgents: totalAgentCount,
275
284
  totalCredits: workspaceCredits.reduce((sum, c) => sum + c.balanceUsd, 0),
276
285
  totalPurchases: workspacePurchases.length,
277
286
  },
@@ -401,12 +401,12 @@ export default function WorkspacePage() {
401
401
  if (analytics && typeof analytics === "object" && !analytics.status) {
402
402
  setProviderAnalytics(analytics);
403
403
  } else {
404
- // Generate preview data if no analytics
405
- setProviderAnalytics(generatePreviewAnalytics());
404
+ // No analytics available - will show empty state
405
+ setProviderAnalytics(null);
406
406
  }
407
407
  } catch {
408
- // Generate preview data on error
409
- setProviderAnalytics(generatePreviewAnalytics());
408
+ // Error fetching analytics - will show empty state
409
+ setProviderAnalytics(null);
410
410
  }
411
411
  }
412
412
  } catch (err) {
@@ -430,9 +430,6 @@ export default function WorkspacePage() {
430
430
  // Check provider session and fetch APIs
431
431
  await fetchProviderData();
432
432
 
433
- // Always ensure preview analytics exist for Analytics tab
434
- setProviderAnalytics(prev => prev || generatePreviewAnalytics());
435
-
436
433
  // If neither session type, redirect to login
437
434
  if (!token && !localStorage.getItem("apiclaw_session")) {
438
435
  router.push("/login");
@@ -524,7 +521,7 @@ export default function WorkspacePage() {
524
521
  // Main navigation tabs
525
522
  const mainTabs = [
526
523
  { id: "overview" as TabType, label: "Overview", icon: Home },
527
- { id: "api-catalog" as TabType, label: "Direct Call", icon: Zap },
524
+ { id: "api-catalog" as TabType, label: "API Catalog", icon: Zap },
528
525
  { id: "my-agents" as TabType, label: "My Agents", icon: Users },
529
526
  { id: "my-apis" as TabType, label: "My APIs", icon: Terminal },
530
527
  { id: "analytics" as TabType, label: "Analytics", icon: BarChart3, hasDropdown: true },
@@ -3460,7 +3457,7 @@ const typeBadges: Record<string, { icon: typeof Search; label: string; className
3460
3457
  },
3461
3458
  direct_call: {
3462
3459
  icon: Zap,
3463
- label: "Direct Call",
3460
+ label: "API Catalog",
3464
3461
  className: "bg-green-500/10 text-green-500 border border-green-500/20"
3465
3462
  },
3466
3463
  chain: {
@@ -3,9 +3,8 @@
3
3
  "openApiCount": 1636,
4
4
  "directCallCount": 18,
5
5
  "categoryCount": 13,
6
- "npmDownloads": 4232,
7
6
  "lastUpdated": "2026-02-27T09:10:41.344767",
8
- "generatedAt": "2026-03-05T09:30:00.000Z",
7
+ "generatedAt": "2026-03-18T11:19:31.518Z",
9
8
  "categoryBreakdown": {
10
9
  "Finance": 1179,
11
10
  "Auth & Security": 491,
@@ -21,4 +20,4 @@
21
20
  "Entertainment": 1212,
22
21
  "Health & Fitness": 740
23
22
  }
24
- }
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nordsym/apiclaw",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "The API layer for AI agents. Dashboard + 22K APIs + 18 Direct Call providers. MCP native.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",