@nordsym/apiclaw 1.5.5 → 1.5.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.
@@ -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
@@ -364,8 +364,9 @@ export const getLogStats = query({
364
364
  }))
365
365
  .sort((a, b) => a.date.localeCompare(b.date));
366
366
 
367
- // Get unique providers for filter dropdown
367
+ // Get unique providers and agents for filter dropdowns
368
368
  const providers = [...new Set(logs.map((l) => l.provider))].sort();
369
+ const agents = [...new Set(logs.map((l) => l.subagentId || "main"))].sort();
369
370
 
370
371
  return {
371
372
  totalCalls,
@@ -376,6 +377,7 @@ export const getLogStats = query({
376
377
  byProvider,
377
378
  byDay,
378
379
  providers,
380
+ agents,
379
381
  };
380
382
  },
381
383
  });
@@ -514,6 +516,8 @@ export const createProxyLog = mutation({
514
516
  sessionToken: v.optional(v.string()),
515
517
  },
516
518
  handler: async (ctx, { workspaceId, provider, action, subagentId, sessionToken }) => {
519
+ const now = Date.now();
520
+
517
521
  await ctx.db.insert("apiLogs", {
518
522
  workspaceId,
519
523
  provider,
@@ -522,9 +526,25 @@ export const createProxyLog = mutation({
522
526
  sessionToken: sessionToken || "proxy",
523
527
  status: "success",
524
528
  latencyMs: 0, // Proxy calls don't track latency
525
- createdAt: Date.now(),
529
+ createdAt: now,
526
530
  });
527
531
 
532
+ // Update workspace lastActiveAt (main agent activity)
533
+ await ctx.db.patch(workspaceId, { lastActiveAt: now });
534
+
535
+ // If this is a subagent call, update that subagent's timestamp
536
+ if (subagentId && subagentId !== "unknown" && subagentId !== "main") {
537
+ const subagent = await ctx.db
538
+ .query("subagents")
539
+ .withIndex("by_workspaceId_subagentId", (q) =>
540
+ q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
541
+ .first();
542
+
543
+ if (subagent) {
544
+ await ctx.db.patch(subagent._id, { lastActiveAt: now });
545
+ }
546
+ }
547
+
528
548
  return { success: true };
529
549
  },
530
550
  });
package/convex/schema.ts CHANGED
@@ -84,6 +84,8 @@ export default defineSchema({
84
84
  pauseOnBudgetExceeded: v.optional(v.boolean()), // If true, block execution when budget exceeded
85
85
  monthlySpendCents: v.optional(v.number()), // Current month's spend in cents
86
86
  lastSpendResetAt: v.optional(v.number()), // When monthly spend was last reset
87
+ // Activity tracking
88
+ lastActiveAt: v.optional(v.number()), // Last API call timestamp (main agent)
87
89
  createdAt: v.number(),
88
90
  updatedAt: v.number(),
89
91
  })
@@ -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
  },
@@ -60,8 +60,6 @@ export async function GET() {
60
60
  >
61
61
  <span>{statsData.apiCount.toLocaleString()}+ APIs</span>
62
62
  <span>•</span>
63
- <span>{statsData.directCallCount} Direct Call</span>
64
- <span>•</span>
65
63
  <span>MCP Native</span>
66
64
  </div>
67
65
  <div
@@ -14,10 +14,8 @@ import { PhoneDemo } from "@/components/demo";
14
14
  import { AITestimonials } from "@/components/AITestimonials";
15
15
 
16
16
  const stats = [
17
- { number: "4,232+", label: "Installs", live: true },
18
17
  { number: statsData.apiCount.toLocaleString(), label: "APIs Indexed", live: true },
19
18
  { number: statsData.openApiCount.toLocaleString(), label: "Open APIs", live: true },
20
- { number: statsData.directCallCount.toString(), label: "Direct Call", live: true },
21
19
  { number: statsData.categoryCount.toString(), label: "Categories", live: false },
22
20
  ];
23
21
 
@@ -211,7 +209,7 @@ Instant highlights:
211
209
  • Code sandbox (E2B)
212
210
  • Multi-LLM routing (OpenRouter)
213
211
 
214
- ${statsData.directCallCount}+ Direct Call providers across categories:
212
+ Direct Call providers across categories:
215
213
  AI/ML: Replicate, OpenRouter, Groq, Mistral, Cohere, Together AI, Stability AI
216
214
  Voice: ElevenLabs, Deepgram, AssemblyAI
217
215
  Search: Brave Search, Serper, Firecrawl
@@ -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: {
@@ -3489,6 +3486,7 @@ interface ApiLogEntry {
3489
3486
  status: "success" | "error";
3490
3487
  latencyMs: number;
3491
3488
  errorMessage?: string;
3489
+ subagentId?: string;
3492
3490
  createdAt: number;
3493
3491
  }
3494
3492
 
@@ -3519,7 +3517,9 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3519
3517
  const [isLoading, setIsLoading] = useState(true);
3520
3518
  const [statusFilter, setStatusFilter] = useState<"all" | "success" | "error">("all");
3521
3519
  const [providerFilter, setProviderFilter] = useState<string>("all");
3520
+ const [agentFilter, setAgentFilter] = useState<string>("all");
3522
3521
  const [providers, setProviders] = useState<string[]>([]);
3522
+ const [agents, setAgents] = useState<string[]>([]);
3523
3523
  const [hasMore, setHasMore] = useState(false);
3524
3524
  const [nextCursor, setNextCursor] = useState<number | undefined>();
3525
3525
  const [loadingMore, setLoadingMore] = useState(false);
@@ -3548,6 +3548,7 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3548
3548
  cursor,
3549
3549
  status: statusFilter,
3550
3550
  provider: providerFilter === "all" ? undefined : providerFilter,
3551
+ subagentId: agentFilter === "all" ? undefined : agentFilter,
3551
3552
  },
3552
3553
  }),
3553
3554
  });
@@ -3605,7 +3606,7 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3605
3606
  setIsLoading(false);
3606
3607
  setLoadingMore(false);
3607
3608
  }
3608
- }, [sessionToken, statusFilter, providerFilter, nextCursor]);
3609
+ }, [sessionToken, statusFilter, providerFilter, agentFilter, nextCursor]);
3609
3610
 
3610
3611
  const fetchStats = useCallback(async () => {
3611
3612
  if (!sessionToken) return;
@@ -3624,6 +3625,7 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3624
3625
  const result = statsData.value || statsData;
3625
3626
  setStats(result);
3626
3627
  setProviders(result.providers || []);
3628
+ setAgents(result.agents || []);
3627
3629
  } catch (err) {
3628
3630
  console.error("Error fetching stats:", err);
3629
3631
  }
@@ -3695,6 +3697,18 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3695
3697
  <option key={p} value={p}>{p}</option>
3696
3698
  ))}
3697
3699
  </select>
3700
+
3701
+ <select
3702
+ value={agentFilter}
3703
+ onChange={(e) => setAgentFilter(e.target.value)}
3704
+ className="px-3 py-2 rounded-lg border border-[var(--border)] bg-[var(--background)] text-sm focus:outline-none focus:ring-2 focus:ring-[#ef4444]/50"
3705
+ >
3706
+ <option value="all">All Agents</option>
3707
+ <option value="main">Main Agent</option>
3708
+ {agents.filter(a => a !== "main" && a !== "unknown").map((a) => (
3709
+ <option key={a} value={a}>{a}</option>
3710
+ ))}
3711
+ </select>
3698
3712
  </div>
3699
3713
 
3700
3714
  {/* Stats Cards */}
@@ -3756,6 +3770,7 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3756
3770
  <thead className="bg-[var(--surface)]">
3757
3771
  <tr>
3758
3772
  <th className="px-4 py-3 text-left text-sm font-medium text-[var(--text-muted)]">Type</th>
3773
+ <th className="px-4 py-3 text-left text-sm font-medium text-[var(--text-muted)]">Agent</th>
3759
3774
  <th className="px-4 py-3 text-left text-sm font-medium text-[var(--text-muted)]">Time</th>
3760
3775
  <th className="px-4 py-3 text-left text-sm font-medium text-[var(--text-muted)]">Provider</th>
3761
3776
  <th className="px-4 py-3 text-left text-sm font-medium text-[var(--text-muted)]">Action</th>
@@ -3769,6 +3784,13 @@ function LogsTab({ sessionToken }: { sessionToken: string | null }) {
3769
3784
  <td className="px-4 py-3">
3770
3785
  <TypeBadge type={log.type} />
3771
3786
  </td>
3787
+ <td className="px-4 py-3">
3788
+ <span className="text-sm font-mono text-[var(--text-muted)]">
3789
+ {log.type === "direct_call"
3790
+ ? ((log as ApiLogEntry).subagentId || "main")
3791
+ : "—"}
3792
+ </span>
3793
+ </td>
3772
3794
  <td className="px-4 py-3 text-sm text-[var(--text-muted)]">
3773
3795
  {formatTime(log.createdAt)}
3774
3796
  </td>
@@ -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:41:41.087Z",
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.7",
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",