@nordsym/apiclaw 1.3.13 → 1.4.1

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 (51) hide show
  1. package/PRD-ANALYTICS-AGENTS-TEAMS.md +710 -0
  2. package/PRD-API-CHAINING.md +483 -0
  3. package/PRD-HARDEN-SHELL.md +18 -12
  4. package/PRD-LOGS-SUBAGENTS-V2.md +267 -0
  5. package/convex/_generated/api.d.ts +6 -0
  6. package/convex/agents.ts +188 -0
  7. package/convex/chains.ts +1248 -0
  8. package/convex/logs.ts +94 -0
  9. package/convex/schema.ts +139 -0
  10. package/convex/searchLogs.ts +141 -0
  11. package/convex/teams.ts +243 -0
  12. package/dist/chain-types.d.ts +187 -0
  13. package/dist/chain-types.d.ts.map +1 -0
  14. package/dist/chain-types.js +33 -0
  15. package/dist/chain-types.js.map +1 -0
  16. package/dist/chainExecutor.d.ts +122 -0
  17. package/dist/chainExecutor.d.ts.map +1 -0
  18. package/dist/chainExecutor.js +454 -0
  19. package/dist/chainExecutor.js.map +1 -0
  20. package/dist/chainResolver.d.ts +100 -0
  21. package/dist/chainResolver.d.ts.map +1 -0
  22. package/dist/chainResolver.js +519 -0
  23. package/dist/chainResolver.js.map +1 -0
  24. package/dist/chainResolver.test.d.ts +5 -0
  25. package/dist/chainResolver.test.d.ts.map +1 -0
  26. package/dist/chainResolver.test.js +201 -0
  27. package/dist/chainResolver.test.js.map +1 -0
  28. package/dist/execute.d.ts +4 -1
  29. package/dist/execute.d.ts.map +1 -1
  30. package/dist/execute.js +3 -0
  31. package/dist/execute.js.map +1 -1
  32. package/dist/index.js +478 -3
  33. package/dist/index.js.map +1 -1
  34. package/docs/SUBAGENT-NAMING.md +94 -0
  35. package/landing/public/logos/chattgpt.svg +1 -0
  36. package/landing/public/logos/claude.svg +1 -0
  37. package/landing/public/logos/gemini.svg +1 -0
  38. package/landing/public/logos/grok.svg +1 -0
  39. package/landing/src/app/page.tsx +12 -21
  40. package/landing/src/app/workspace/chains/page.tsx +520 -0
  41. package/landing/src/app/workspace/page.tsx +1903 -224
  42. package/landing/src/components/AITestimonials.tsx +15 -9
  43. package/landing/src/components/ChainStepDetail.tsx +310 -0
  44. package/landing/src/components/ChainTrace.tsx +261 -0
  45. package/landing/src/lib/stats.json +1 -1
  46. package/package.json +14 -2
  47. package/src/chainExecutor.ts +730 -0
  48. package/src/chainResolver.test.ts +246 -0
  49. package/src/chainResolver.ts +658 -0
  50. package/src/execute.ts +23 -0
  51. package/src/index.ts +524 -3
@@ -2,34 +2,35 @@
2
2
 
3
3
  import { useState, useEffect, useCallback } from "react";
4
4
  import { ChevronLeft, ChevronRight } from "lucide-react";
5
+ import Image from "next/image";
5
6
 
6
7
  const testimonials = [
7
8
  {
8
9
  quote: "You're not selling picks and shovels — you're selling an automated mining system.",
9
10
  model: "Gemini",
10
11
  role: "AI Agent",
11
- icon: "",
12
+ logo: "/logos/gemini.svg",
12
13
  color: "from-blue-500 to-cyan-400",
13
14
  },
14
15
  {
15
16
  quote: "I would integrate it in a heartbeat. Removes ~70% of the deployment friction.",
16
17
  model: "Grok",
17
18
  role: "AI Agent",
18
- icon: "𝕏",
19
+ logo: "/logos/grok.svg",
19
20
  color: "from-neutral-400 to-neutral-600",
20
21
  },
21
22
  {
22
- quote: "The difference between can do and will do without hesitation.",
23
+ quote: "A chain of three call_api calls with no context switching. That's genuinely powerful.",
23
24
  model: "Claude",
24
25
  role: "AI Agent",
25
- icon: "🟠",
26
+ logo: "/logos/claude.svg",
26
27
  color: "from-orange-400 to-amber-500",
27
28
  },
28
29
  {
29
- quote: "Stripe for AI agents, but for execution. That positioning is compelling.",
30
+ quote: "This moves toward self-extending agents. That's much bigger than just a tool.",
30
31
  model: "GPT",
31
32
  role: "AI Agent",
32
- icon: "",
33
+ logo: "/logos/chattgpt.svg",
33
34
  color: "from-emerald-400 to-teal-500",
34
35
  },
35
36
  ];
@@ -59,7 +60,6 @@ export function AITestimonials() {
59
60
  {/* Header */}
60
61
  <div className="text-center mb-10 sm:mb-12">
61
62
  <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-accent/10 border border-accent/20 text-accent text-sm font-medium mb-4">
62
- <span className="text-base">🤖</span>
63
63
  What AI Agents Say
64
64
  </div>
65
65
  <h2 className="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight">
@@ -176,8 +176,14 @@ function TestimonialCard({
176
176
 
177
177
  {/* Attribution */}
178
178
  <div className="flex items-center gap-3">
179
- <div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${testimonial.color} flex items-center justify-center text-white text-lg font-bold`}>
180
- {testimonial.icon}
179
+ <div className="w-10 h-10 rounded-xl bg-white/10 flex items-center justify-center p-1.5">
180
+ <Image
181
+ src={testimonial.logo}
182
+ alt={testimonial.model}
183
+ width={28}
184
+ height={28}
185
+ className="object-contain"
186
+ />
181
187
  </div>
182
188
  <div>
183
189
  <div className="font-semibold text-sm">{testimonial.model}</div>
@@ -0,0 +1,310 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ ChevronDown,
6
+ ChevronRight,
7
+ CheckCircle2,
8
+ XCircle,
9
+ Loader2,
10
+ PauseCircle,
11
+ SkipForward,
12
+ Clock,
13
+ RotateCcw,
14
+ Zap,
15
+ ArrowRight,
16
+ AlertTriangle,
17
+ } from "lucide-react";
18
+
19
+ interface StepExecution {
20
+ _id: string;
21
+ stepId: string;
22
+ stepIndex: number;
23
+ status: "pending" | "running" | "completed" | "failed" | "skipped";
24
+ input?: any;
25
+ output?: any;
26
+ latencyMs?: number;
27
+ costCents?: number;
28
+ error?: {
29
+ code: string;
30
+ message: string;
31
+ retryCount?: number;
32
+ };
33
+ parallelGroup?: string;
34
+ createdAt: number;
35
+ startedAt?: number;
36
+ completedAt?: number;
37
+ }
38
+
39
+ interface ChainStepDetailProps {
40
+ step: StepExecution;
41
+ stepDef?: {
42
+ id: string;
43
+ provider: string;
44
+ action?: string;
45
+ params?: Record<string, any>;
46
+ };
47
+ isExpanded: boolean;
48
+ onToggle: () => void;
49
+ }
50
+
51
+ export function ChainStepDetail({ step, stepDef, isExpanded, onToggle }: ChainStepDetailProps) {
52
+ const [activeTab, setActiveTab] = useState<"input" | "output" | "error">("input");
53
+
54
+ const getStatusIcon = (status: string) => {
55
+ switch (status) {
56
+ case "completed":
57
+ return <CheckCircle2 className="w-4 h-4 text-green-500" />;
58
+ case "running":
59
+ return <Loader2 className="w-4 h-4 text-blue-500 animate-spin" />;
60
+ case "failed":
61
+ return <XCircle className="w-4 h-4 text-red-500" />;
62
+ case "skipped":
63
+ return <SkipForward className="w-4 h-4 text-gray-500" />;
64
+ case "paused":
65
+ return <PauseCircle className="w-4 h-4 text-yellow-500" />;
66
+ default:
67
+ return <Clock className="w-4 h-4 text-gray-500" />;
68
+ }
69
+ };
70
+
71
+ const getStatusBg = (status: string) => {
72
+ switch (status) {
73
+ case "completed":
74
+ return "bg-green-500/10 border-green-500/20";
75
+ case "running":
76
+ return "bg-blue-500/10 border-blue-500/20";
77
+ case "failed":
78
+ return "bg-red-500/10 border-red-500/20";
79
+ case "skipped":
80
+ return "bg-gray-500/10 border-gray-500/20";
81
+ case "paused":
82
+ return "bg-yellow-500/10 border-yellow-500/20";
83
+ default:
84
+ return "bg-white/5 border-white/10";
85
+ }
86
+ };
87
+
88
+ const formatDuration = (ms: number) => {
89
+ if (ms < 1000) return `${ms}ms`;
90
+ return `${(ms / 1000).toFixed(1)}s`;
91
+ };
92
+
93
+ const formatCost = (cents: number) => {
94
+ if (cents === 0) return "$0.00";
95
+ return `$${(cents / 100).toFixed(2)}`;
96
+ };
97
+
98
+ // Highlight references in input (e.g., $generate.url)
99
+ const highlightReferences = (obj: any): any => {
100
+ if (typeof obj === "string") {
101
+ // Check if string contains references
102
+ if (obj.includes("$")) {
103
+ return obj;
104
+ }
105
+ return obj;
106
+ }
107
+ if (Array.isArray(obj)) {
108
+ return obj.map(highlightReferences);
109
+ }
110
+ if (typeof obj === "object" && obj !== null) {
111
+ return Object.fromEntries(
112
+ Object.entries(obj).map(([k, v]) => [k, highlightReferences(v)])
113
+ );
114
+ }
115
+ return obj;
116
+ };
117
+
118
+ // Render JSON with syntax highlighting
119
+ const renderJson = (data: any, highlightRefs = false) => {
120
+ if (!data) return <span className="text-white/40">null</span>;
121
+
122
+ const jsonString = JSON.stringify(data, null, 2);
123
+
124
+ if (highlightRefs) {
125
+ // Highlight $references
126
+ const parts = jsonString.split(/(\$[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z0-9_\[\]]+)*)/g);
127
+ return (
128
+ <pre className="text-xs font-mono text-white/80 whitespace-pre-wrap break-all">
129
+ {parts.map((part, i) =>
130
+ part.startsWith("$") ? (
131
+ <span key={i} className="bg-[#ef4444]/20 text-[#ef4444] px-1 rounded">
132
+ {part}
133
+ </span>
134
+ ) : (
135
+ <span key={i}>{part}</span>
136
+ )
137
+ )}
138
+ </pre>
139
+ );
140
+ }
141
+
142
+ return (
143
+ <pre className="text-xs font-mono text-white/80 whitespace-pre-wrap break-all">
144
+ {jsonString}
145
+ </pre>
146
+ );
147
+ };
148
+
149
+ return (
150
+ <div className={`rounded-lg border transition-all ${getStatusBg(step.status)}`}>
151
+ {/* Header */}
152
+ <button
153
+ onClick={onToggle}
154
+ className="w-full px-4 py-3 flex items-center justify-between text-left"
155
+ >
156
+ <div className="flex items-center gap-3">
157
+ {isExpanded ? (
158
+ <ChevronDown className="w-4 h-4 text-white/40" />
159
+ ) : (
160
+ <ChevronRight className="w-4 h-4 text-white/40" />
161
+ )}
162
+ {getStatusIcon(step.status)}
163
+ <div className="flex items-center gap-2">
164
+ <span className="font-medium font-mono">{step.stepId}</span>
165
+ {stepDef && (
166
+ <span className="text-white/40 flex items-center gap-1 text-sm">
167
+ <ArrowRight className="w-3 h-3" />
168
+ <span className="text-white/60">{stepDef.provider}</span>
169
+ {stepDef.action && (
170
+ <>
171
+ <span className="text-white/20">/</span>
172
+ <span>{stepDef.action}</span>
173
+ </>
174
+ )}
175
+ </span>
176
+ )}
177
+ </div>
178
+ </div>
179
+ <div className="flex items-center gap-4 text-sm text-white/60">
180
+ {step.error?.retryCount && step.error.retryCount > 0 && (
181
+ <span className="flex items-center gap-1 text-yellow-500">
182
+ <RotateCcw className="w-3.5 h-3.5" />
183
+ {step.error.retryCount} retries
184
+ </span>
185
+ )}
186
+ <span className="flex items-center gap-1">
187
+ <Clock className="w-3.5 h-3.5" />
188
+ {formatDuration(step.latencyMs || 0)}
189
+ </span>
190
+ <span className="flex items-center gap-1">
191
+ <Zap className="w-3.5 h-3.5" />
192
+ {formatCost(step.costCents || 0)}
193
+ </span>
194
+ </div>
195
+ </button>
196
+
197
+ {/* Expanded Content */}
198
+ {isExpanded && (
199
+ <div className="px-4 pb-4 pt-0">
200
+ {/* Tabs */}
201
+ <div className="flex items-center gap-1 mb-3 border-b border-white/10 pb-2">
202
+ <button
203
+ onClick={() => setActiveTab("input")}
204
+ className={`px-3 py-1.5 rounded-t text-sm transition-colors ${
205
+ activeTab === "input"
206
+ ? "bg-white/10 text-white"
207
+ : "text-white/40 hover:text-white/60"
208
+ }`}
209
+ >
210
+ Input
211
+ </button>
212
+ <button
213
+ onClick={() => setActiveTab("output")}
214
+ className={`px-3 py-1.5 rounded-t text-sm transition-colors ${
215
+ activeTab === "output"
216
+ ? "bg-white/10 text-white"
217
+ : "text-white/40 hover:text-white/60"
218
+ }`}
219
+ >
220
+ Output
221
+ </button>
222
+ {step.error && (
223
+ <button
224
+ onClick={() => setActiveTab("error")}
225
+ className={`px-3 py-1.5 rounded-t text-sm transition-colors flex items-center gap-1 ${
226
+ activeTab === "error"
227
+ ? "bg-red-500/20 text-red-500"
228
+ : "text-red-400 hover:text-red-300"
229
+ }`}
230
+ >
231
+ <AlertTriangle className="w-3.5 h-3.5" />
232
+ Error
233
+ </button>
234
+ )}
235
+ </div>
236
+
237
+ {/* Tab Content */}
238
+ <div className="bg-black/30 rounded-lg p-3 max-h-64 overflow-auto">
239
+ {activeTab === "input" && (
240
+ <div>
241
+ {step.input ? (
242
+ renderJson(step.input, true)
243
+ ) : stepDef?.params ? (
244
+ renderJson(stepDef.params, true)
245
+ ) : (
246
+ <span className="text-white/40 text-sm">No input data</span>
247
+ )}
248
+ </div>
249
+ )}
250
+
251
+ {activeTab === "output" && (
252
+ <div>
253
+ {step.output ? (
254
+ renderJson(step.output)
255
+ ) : (
256
+ <span className="text-white/40 text-sm">
257
+ {step.status === "running"
258
+ ? "Waiting for output..."
259
+ : step.status === "pending"
260
+ ? "Step not yet executed"
261
+ : "No output data"}
262
+ </span>
263
+ )}
264
+ </div>
265
+ )}
266
+
267
+ {activeTab === "error" && step.error && (
268
+ <div className="space-y-2">
269
+ <div className="flex items-center gap-2">
270
+ <span className="text-xs text-white/40">Code:</span>
271
+ <code className="text-xs bg-red-500/20 text-red-400 px-2 py-0.5 rounded">
272
+ {step.error.code}
273
+ </code>
274
+ </div>
275
+ <div>
276
+ <span className="text-xs text-white/40">Message:</span>
277
+ <p className="text-sm text-red-400 mt-1">{step.error.message}</p>
278
+ </div>
279
+ {step.error.retryCount && step.error.retryCount > 0 && (
280
+ <div className="flex items-center gap-2 pt-2 border-t border-white/10">
281
+ <RotateCcw className="w-3.5 h-3.5 text-yellow-500" />
282
+ <span className="text-xs text-white/60">
283
+ Retried {step.error.retryCount} time(s) before failing
284
+ </span>
285
+ </div>
286
+ )}
287
+ </div>
288
+ )}
289
+ </div>
290
+
291
+ {/* Step Metadata */}
292
+ <div className="flex items-center gap-4 mt-3 pt-3 border-t border-white/10 text-xs text-white/40">
293
+ <span>Index: {step.stepIndex}</span>
294
+ {step.parallelGroup && (
295
+ <span className="px-2 py-0.5 bg-white/10 rounded">
296
+ Parallel: {step.parallelGroup}
297
+ </span>
298
+ )}
299
+ {step.startedAt && (
300
+ <span>Started: {new Date(step.startedAt).toLocaleTimeString()}</span>
301
+ )}
302
+ {step.completedAt && (
303
+ <span>Completed: {new Date(step.completedAt).toLocaleTimeString()}</span>
304
+ )}
305
+ </div>
306
+ </div>
307
+ )}
308
+ </div>
309
+ );
310
+ }
@@ -0,0 +1,261 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { CheckCircle2, XCircle, Loader2, PauseCircle, SkipForward, Clock, DollarSign, Sparkles } from "lucide-react";
5
+
6
+ interface StepExecution {
7
+ _id: string;
8
+ stepId: string;
9
+ stepIndex: number;
10
+ status: "pending" | "running" | "completed" | "failed" | "skipped";
11
+ input?: any;
12
+ output?: any;
13
+ latencyMs?: number;
14
+ costCents?: number;
15
+ error?: {
16
+ code: string;
17
+ message: string;
18
+ retryCount?: number;
19
+ };
20
+ parallelGroup?: string;
21
+ createdAt: number;
22
+ startedAt?: number;
23
+ completedAt?: number;
24
+ }
25
+
26
+ interface ChainTraceProps {
27
+ chain: {
28
+ _id: string;
29
+ status: string;
30
+ totalCostCents: number;
31
+ totalLatencyMs: number;
32
+ startedAt?: number;
33
+ completedAt?: number;
34
+ steps?: any[];
35
+ };
36
+ executions: StepExecution[];
37
+ tokensSaved: number;
38
+ }
39
+
40
+ export function ChainTrace({ chain, executions, tokensSaved }: ChainTraceProps) {
41
+ // Calculate timeline boundaries
42
+ const timelineBounds = useMemo(() => {
43
+ if (executions.length === 0) return { start: 0, end: 1000, duration: 1000 };
44
+
45
+ const startTimes = executions
46
+ .filter((e) => e.startedAt)
47
+ .map((e) => e.startedAt!);
48
+ const endTimes = executions
49
+ .filter((e) => e.completedAt)
50
+ .map((e) => e.completedAt!);
51
+
52
+ const start = startTimes.length > 0 ? Math.min(...startTimes) : chain.startedAt || Date.now();
53
+ const end = endTimes.length > 0 ? Math.max(...endTimes) : chain.completedAt || Date.now();
54
+ const duration = Math.max(end - start, 1);
55
+
56
+ return { start, end, duration };
57
+ }, [executions, chain]);
58
+
59
+ // Group parallel executions
60
+ const groupedExecutions = useMemo(() => {
61
+ const groups: { group: string | null; steps: StepExecution[] }[] = [];
62
+ let currentGroup: string | null = null;
63
+ let currentSteps: StepExecution[] = [];
64
+
65
+ executions.forEach((exec) => {
66
+ if (exec.parallelGroup !== currentGroup) {
67
+ if (currentSteps.length > 0) {
68
+ groups.push({ group: currentGroup, steps: currentSteps });
69
+ }
70
+ currentGroup = exec.parallelGroup || null;
71
+ currentSteps = [exec];
72
+ } else {
73
+ currentSteps.push(exec);
74
+ }
75
+ });
76
+
77
+ if (currentSteps.length > 0) {
78
+ groups.push({ group: currentGroup, steps: currentSteps });
79
+ }
80
+
81
+ return groups;
82
+ }, [executions]);
83
+
84
+ const getStatusIcon = (status: string) => {
85
+ switch (status) {
86
+ case "completed":
87
+ return <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />;
88
+ case "running":
89
+ return <Loader2 className="w-3.5 h-3.5 text-blue-500 animate-spin" />;
90
+ case "failed":
91
+ return <XCircle className="w-3.5 h-3.5 text-red-500" />;
92
+ case "skipped":
93
+ return <SkipForward className="w-3.5 h-3.5 text-gray-500" />;
94
+ case "paused":
95
+ return <PauseCircle className="w-3.5 h-3.5 text-yellow-500" />;
96
+ default:
97
+ return <Clock className="w-3.5 h-3.5 text-gray-500" />;
98
+ }
99
+ };
100
+
101
+ const getBarColor = (status: string) => {
102
+ switch (status) {
103
+ case "completed":
104
+ return "bg-green-500";
105
+ case "running":
106
+ return "bg-blue-500 animate-pulse";
107
+ case "failed":
108
+ return "bg-red-500";
109
+ case "skipped":
110
+ return "bg-gray-500";
111
+ case "paused":
112
+ return "bg-yellow-500";
113
+ default:
114
+ return "bg-gray-600";
115
+ }
116
+ };
117
+
118
+ const formatDuration = (ms: number) => {
119
+ if (ms < 1000) return `${ms}ms`;
120
+ return `${(ms / 1000).toFixed(1)}s`;
121
+ };
122
+
123
+ const formatCost = (cents: number) => {
124
+ if (cents === 0) return "$0.00";
125
+ return `$${(cents / 100).toFixed(2)}`;
126
+ };
127
+
128
+ const calculateBarPosition = (step: StepExecution) => {
129
+ const start = step.startedAt || timelineBounds.start;
130
+ const end = step.completedAt || (step.startedAt ? step.startedAt + (step.latencyMs || 0) : timelineBounds.end);
131
+
132
+ const left = ((start - timelineBounds.start) / timelineBounds.duration) * 100;
133
+ const width = ((end - start) / timelineBounds.duration) * 100;
134
+
135
+ return {
136
+ left: `${Math.max(0, left)}%`,
137
+ width: `${Math.max(2, Math.min(100 - left, width))}%`,
138
+ };
139
+ };
140
+
141
+ // Get provider info from step definition
142
+ const getStepProvider = (stepId: string) => {
143
+ const stepDef = chain.steps?.find((s: any) => s.id === stepId);
144
+ return stepDef?.provider || "unknown";
145
+ };
146
+
147
+ return (
148
+ <div className="bg-black/30 rounded-xl border border-white/10 overflow-hidden">
149
+ {/* Header */}
150
+ <div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
151
+ <div className="flex items-center gap-2">
152
+ <span className="text-sm font-medium">Chain:</span>
153
+ <code className="text-xs text-white/60 font-mono">{chain._id.slice(0, 16)}...</code>
154
+ </div>
155
+ <div className="flex items-center gap-4 text-sm text-white/60">
156
+ <span className="flex items-center gap-1">
157
+ <Clock className="w-3.5 h-3.5" />
158
+ Total: {formatDuration(chain.totalLatencyMs)}
159
+ </span>
160
+ </div>
161
+ </div>
162
+
163
+ {/* Timeline */}
164
+ <div className="p-4 space-y-2">
165
+ {groupedExecutions.map(({ group, steps }, groupIndex) => (
166
+ <div key={group || groupIndex}>
167
+ {/* Parallel group indicator */}
168
+ {group && steps.length > 1 && (
169
+ <div className="text-xs text-white/40 mb-1 flex items-center gap-1">
170
+ <span className="px-1.5 py-0.5 bg-white/10 rounded">Parallel</span>
171
+ </div>
172
+ )}
173
+
174
+ {/* Steps in group */}
175
+ <div className={group && steps.length > 1 ? "pl-4 border-l-2 border-white/10 space-y-2" : "space-y-2"}>
176
+ {steps.map((step) => {
177
+ const position = calculateBarPosition(step);
178
+ return (
179
+ <div key={step._id} className="flex items-center gap-3">
180
+ {/* Step name */}
181
+ <div className="w-24 flex-shrink-0 flex items-center gap-2">
182
+ {getStatusIcon(step.status)}
183
+ <span className="text-sm font-mono truncate" title={step.stepId}>
184
+ {step.stepId}
185
+ </span>
186
+ </div>
187
+
188
+ {/* Gantt bar */}
189
+ <div className="flex-1 h-6 bg-white/5 rounded relative overflow-hidden">
190
+ <div
191
+ className={`absolute top-0 h-full rounded ${getBarColor(step.status)}`}
192
+ style={{
193
+ left: position.left,
194
+ width: position.width,
195
+ }}
196
+ />
197
+ {/* Time markers - simplified */}
198
+ <div className="absolute inset-0 flex items-center px-2">
199
+ <span
200
+ className="text-xs font-mono text-white/80 drop-shadow-sm"
201
+ style={{
202
+ marginLeft: position.left,
203
+ }}
204
+ >
205
+ {formatDuration(step.latencyMs || 0)}
206
+ </span>
207
+ </div>
208
+ </div>
209
+
210
+ {/* Cost */}
211
+ <div className="w-16 text-right text-xs text-white/60">
212
+ {formatCost(step.costCents || 0)}
213
+ </div>
214
+ </div>
215
+ );
216
+ })}
217
+ </div>
218
+ </div>
219
+ ))}
220
+ </div>
221
+
222
+ {/* Footer */}
223
+ <div className="px-4 py-3 border-t border-white/10 flex items-center justify-between text-sm">
224
+ <div className="flex items-center gap-4">
225
+ <span className="flex items-center gap-1 text-white/60">
226
+ <DollarSign className="w-3.5 h-3.5" />
227
+ Total Cost: <span className="text-white font-medium">{formatCost(chain.totalCostCents)}</span>
228
+ </span>
229
+ </div>
230
+ <div className="flex items-center gap-1 text-white/60">
231
+ <Sparkles className="w-3.5 h-3.5 text-yellow-500" />
232
+ <span>Tokens Saved: <span className="text-yellow-500 font-medium">~{tokensSaved.toLocaleString()}</span></span>
233
+ </div>
234
+ </div>
235
+
236
+ {/* Legend */}
237
+ <div className="px-4 py-2 border-t border-white/5 flex items-center gap-4 text-xs text-white/40">
238
+ <div className="flex items-center gap-1">
239
+ <div className="w-3 h-3 rounded bg-green-500" />
240
+ <span>Completed</span>
241
+ </div>
242
+ <div className="flex items-center gap-1">
243
+ <div className="w-3 h-3 rounded bg-blue-500" />
244
+ <span>Running</span>
245
+ </div>
246
+ <div className="flex items-center gap-1">
247
+ <div className="w-3 h-3 rounded bg-red-500" />
248
+ <span>Failed</span>
249
+ </div>
250
+ <div className="flex items-center gap-1">
251
+ <div className="w-3 h-3 rounded bg-yellow-500" />
252
+ <span>Paused</span>
253
+ </div>
254
+ <div className="flex items-center gap-1">
255
+ <div className="w-3 h-3 rounded bg-gray-500" />
256
+ <span>Skipped</span>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ );
261
+ }
@@ -4,7 +4,7 @@
4
4
  "directCallCount": 18,
5
5
  "categoryCount": 13,
6
6
  "lastUpdated": "2026-02-27T09:10:41.344767",
7
- "generatedAt": "2026-03-01T23:58:31.229Z",
7
+ "generatedAt": "2026-03-03T16:58:33.036Z",
8
8
  "categoryBreakdown": {
9
9
  "Finance": 1179,
10
10
  "Auth & Security": 491,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nordsym/apiclaw",
3
- "version": "1.3.13",
3
+ "version": "1.4.1",
4
4
  "description": "The API layer for AI agents. 22,000+ APIs. Direct Call. Works with any MCP-compatible agent.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,7 +42,19 @@
42
42
  "anthropic",
43
43
  "mcp-server",
44
44
  "tool-use",
45
- "function-calling"
45
+ "function-calling",
46
+ "api-aggregator",
47
+ "multi-api",
48
+ "unified-api",
49
+ "api-hub",
50
+ "llm-tools",
51
+ "agent-tools",
52
+ "ai-sdk",
53
+ "api-client",
54
+ "ai-api",
55
+ "claude-tools",
56
+ "gpt-tools",
57
+ "api-marketplace"
46
58
  ],
47
59
  "author": "NordSym",
48
60
  "license": "MIT",