@nordsym/apiclaw 1.3.13 → 1.4.0

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 (44) hide show
  1. package/PRD-API-CHAINING.md +483 -0
  2. package/PRD-HARDEN-SHELL.md +18 -12
  3. package/convex/_generated/api.d.ts +2 -0
  4. package/convex/chains.ts +1095 -0
  5. package/convex/schema.ts +101 -0
  6. package/dist/chain-types.d.ts +187 -0
  7. package/dist/chain-types.d.ts.map +1 -0
  8. package/dist/chain-types.js +33 -0
  9. package/dist/chain-types.js.map +1 -0
  10. package/dist/chainExecutor.d.ts +122 -0
  11. package/dist/chainExecutor.d.ts.map +1 -0
  12. package/dist/chainExecutor.js +454 -0
  13. package/dist/chainExecutor.js.map +1 -0
  14. package/dist/chainResolver.d.ts +100 -0
  15. package/dist/chainResolver.d.ts.map +1 -0
  16. package/dist/chainResolver.js +519 -0
  17. package/dist/chainResolver.js.map +1 -0
  18. package/dist/chainResolver.test.d.ts +5 -0
  19. package/dist/chainResolver.test.d.ts.map +1 -0
  20. package/dist/chainResolver.test.js +201 -0
  21. package/dist/chainResolver.test.js.map +1 -0
  22. package/dist/execute.d.ts +4 -1
  23. package/dist/execute.d.ts.map +1 -1
  24. package/dist/execute.js +3 -0
  25. package/dist/execute.js.map +1 -1
  26. package/dist/index.js +382 -2
  27. package/dist/index.js.map +1 -1
  28. package/landing/public/logos/chattgpt.svg +1 -0
  29. package/landing/public/logos/claude.svg +1 -0
  30. package/landing/public/logos/gemini.svg +1 -0
  31. package/landing/public/logos/grok.svg +1 -0
  32. package/landing/src/app/page.tsx +12 -21
  33. package/landing/src/app/workspace/chains/page.tsx +520 -0
  34. package/landing/src/components/AITestimonials.tsx +15 -9
  35. package/landing/src/components/ChainStepDetail.tsx +310 -0
  36. package/landing/src/components/ChainTrace.tsx +261 -0
  37. package/landing/src/lib/stats.json +1 -1
  38. package/package.json +1 -1
  39. package/src/chain-types.ts +270 -0
  40. package/src/chainExecutor.ts +730 -0
  41. package/src/chainResolver.test.ts +246 -0
  42. package/src/chainResolver.ts +658 -0
  43. package/src/execute.ts +23 -0
  44. package/src/index.ts +423 -2
@@ -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-02T00:02:51.920Z",
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.0",
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",