@nordsym/apiclaw 1.3.8 → 1.3.9

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.
@@ -1462,7 +1462,7 @@ function AgentsTab({
1462
1462
  setTimeout(() => setCopied(false), 2000);
1463
1463
  };
1464
1464
 
1465
- const mcpCommand = "npx @nordsym/apiclaw";
1465
+ const mcpCommand = "npx @nordsym/apiclaw mcp-install";
1466
1466
 
1467
1467
  return (
1468
1468
  <div className="space-y-6">
@@ -3725,7 +3725,7 @@ function DocsTab() {
3725
3725
  </div>
3726
3726
  <div>
3727
3727
  <p className="text-sm text-[var(--text-muted)] mb-2">2. Or run directly:</p>
3728
- <pre className="bg-[var(--background)] rounded-lg p-4 text-sm">npx @nordsym/apiclaw</pre>
3728
+ <pre className="bg-[var(--background)] rounded-lg p-4 text-sm">npx @nordsym/apiclaw mcp-install</pre>
3729
3729
  </div>
3730
3730
  <div>
3731
3731
  <p className="text-sm text-[var(--text-muted)] mb-2">3. Interactive CLI mode:</p>
@@ -4011,101 +4011,6 @@ function FeedbackTab() {
4011
4011
  </div>
4012
4012
  </form>
4013
4013
  </div>
4014
-
4015
- <div className="rounded-2xl border border-[var(--border)] bg-[var(--surface-elevated)] p-6">
4016
- <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
4017
- <h3 className="font-semibold text-lg">Community Feedback</h3>
4018
- <div className="flex flex-wrap gap-2">
4019
- <select
4020
- value={filterType}
4021
- onChange={(e) => setFilterType(e.target.value as typeof filterType)}
4022
- className="px-3 py-1.5 rounded-lg border border-[var(--border)] bg-[var(--background)] text-sm focus:outline-none focus:ring-2 focus:ring-[#ef4444]/50"
4023
- >
4024
- <option value="all">All Types</option>
4025
- <option value="bug">Bugs</option>
4026
- <option value="feature">Features</option>
4027
- <option value="general">General</option>
4028
- </select>
4029
-
4030
- <select
4031
- value={sortBy}
4032
- onChange={(e) => setSortBy(e.target.value as typeof sortBy)}
4033
- className="px-3 py-1.5 rounded-lg border border-[var(--border)] bg-[var(--background)] text-sm focus:outline-none focus:ring-2 focus:ring-[#ef4444]/50"
4034
- >
4035
- <option value="votes">Most Votes</option>
4036
- <option value="recent">Most Recent</option>
4037
- </select>
4038
- </div>
4039
- </div>
4040
-
4041
- {loading ? (
4042
- <div className="text-center py-8">
4043
- <Loader2 className="w-8 h-8 text-[#ef4444] animate-spin mx-auto mb-2" />
4044
- <p className="text-sm text-[var(--text-muted)]">Loading feedback...</p>
4045
- </div>
4046
- ) : feedbackList.length === 0 ? (
4047
- <div className="text-center py-12 rounded-xl border border-dashed border-[var(--border)] bg-[var(--surface)]/50">
4048
- <MessageSquare className="w-12 h-12 text-[var(--text-muted)] mx-auto mb-3" />
4049
- <h4 className="font-semibold mb-1">No Feedback Yet</h4>
4050
- <p className="text-sm text-[var(--text-muted)]">
4051
- Be the first to share your thoughts!
4052
- </p>
4053
- </div>
4054
- ) : (
4055
- <div className="space-y-3">
4056
- {feedbackList.map((item) => (
4057
- <div
4058
- key={item._id}
4059
- className={`flex gap-3 p-4 rounded-xl border transition ${
4060
- item.isOwn
4061
- ? "border-[#ef4444]/30 bg-[#ef4444]/5"
4062
- : "border-[var(--border)] bg-[var(--surface)]"
4063
- }`}
4064
- >
4065
- <div className="flex flex-col items-center gap-1 min-w-[40px]">
4066
- <button
4067
- onClick={() => handleVote(item._id, "up")}
4068
- disabled={votingId === item._id}
4069
- className={`p-1 rounded hover:bg-[var(--background)] transition ${
4070
- item.hasVoted ? "text-[#ef4444]" : "text-[var(--text-muted)]"
4071
- }`}
4072
- >
4073
- <ChevronUp className="w-5 h-5" />
4074
- </button>
4075
- <span className={`text-sm font-bold ${item.votes > 0 ? "text-[#ef4444]" : item.votes < 0 ? "text-red-500" : "text-[var(--text-muted)]"}`}>
4076
- {item.votes}
4077
- </span>
4078
- <button
4079
- onClick={() => handleVote(item._id, "down")}
4080
- disabled={votingId === item._id}
4081
- className="p-1 rounded text-[var(--text-muted)] hover:bg-[var(--background)] transition"
4082
- >
4083
- <ChevronDown className="w-5 h-5" />
4084
- </button>
4085
- </div>
4086
-
4087
- <div className="flex-1 min-w-0">
4088
- <p className="text-[var(--text-primary)] mb-2">{item.content}</p>
4089
- <div className="flex flex-wrap items-center gap-2 text-xs">
4090
- <span className={`px-2 py-0.5 rounded-full capitalize ${getTypeBadge(item.type)}`}>
4091
- {item.type}
4092
- </span>
4093
- <span className={`px-2 py-0.5 rounded-full capitalize ${getStatusBadge(item.status)}`}>
4094
- {item.status}
4095
- </span>
4096
- <span className="text-[var(--text-muted)]">
4097
- {formatDate(item.createdAt)}
4098
- </span>
4099
- {item.isOwn && (
4100
- <span className="text-[#ef4444]">• You</span>
4101
- )}
4102
- </div>
4103
- </div>
4104
- </div>
4105
- ))}
4106
- </div>
4107
- )}
4108
- </div>
4109
4014
  </div>
4110
4015
  );
4111
4016
  }
@@ -31,7 +31,7 @@ export function HeroTabs() {
31
31
 
32
32
  const configSnippetJson = selectedClient === "chatgpt" ? chatGptInstructions : jsonConfig;
33
33
 
34
- const terminalCommand = "npx @nordsym/apiclaw";
34
+ const terminalCommand = "npx @nordsym/apiclaw mcp-install";
35
35
 
36
36
  const copyConfig = () => {
37
37
  navigator.clipboard.writeText(configSnippetJson);
@@ -126,7 +126,7 @@ export function HeroTabs() {
126
126
  <div className="code-preview-header">terminal</div>
127
127
  <div className="code-preview-body">
128
128
  <pre className="text-sm">
129
- <span className="text-green-400">$</span> <span className="text-blue-400">npx</span> @nordsym/apiclaw
129
+ <span className="text-green-400">$</span> <span className="text-blue-400">npx</span> @nordsym/apiclaw mcp-install
130
130
  </pre>
131
131
  </div>
132
132
  </div>
@@ -0,0 +1,423 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import Image from "next/image";
5
+
6
+ interface WithMessage {
7
+ role: "user" | "assistant";
8
+ text: string;
9
+ meta?: string;
10
+ typing?: boolean;
11
+ image?: boolean;
12
+ currency?: boolean;
13
+ success?: boolean;
14
+ search?: boolean;
15
+ models?: { name: string; match: string }[];
16
+ results?: { name: string; match: string; cost: string }[];
17
+ }
18
+
19
+ interface WithoutMessage {
20
+ role: "step";
21
+ text: string;
22
+ }
23
+
24
+ type Message = WithMessage | WithoutMessage;
25
+
26
+ // Example 1: Direct Call - Replicate
27
+ const DirectCallExample: WithMessage[] = [
28
+ { role: "user", text: "Generate a product photo of a coffee mug" },
29
+ { role: "assistant", text: "Direct Call → Replicate", search: true },
30
+ {
31
+ role: "assistant",
32
+ text: "Selecting model...",
33
+ models: [
34
+ { name: "Flux Pro", match: "Best for products" },
35
+ { name: "SDXL", match: "Fast generation" },
36
+ { name: "Stable Diffusion 3", match: "Versatile" },
37
+ ]
38
+ },
39
+ { role: "assistant", text: "Using Flux Pro", meta: "via Replicate Direct Call" },
40
+ { role: "assistant", text: "Generating...", typing: true },
41
+ { role: "assistant", text: "Done", image: true, success: true },
42
+ ];
43
+
44
+ // Example 2: Open API - Currency
45
+ const OpenAPIExample: WithMessage[] = [
46
+ { role: "user", text: "What's the USD to SEK exchange rate?" },
47
+ { role: "assistant", text: "Open API → Frankfurter", search: true },
48
+ { role: "assistant", text: "No API key needed", meta: "Free, open access" },
49
+ { role: "assistant", text: "Fetching rate...", typing: true },
50
+ { role: "assistant", text: "Current rate", currency: true, success: true },
51
+ ];
52
+
53
+ // Example 3: Discovery - Search 22k APIs
54
+ const DiscoveryExample: WithMessage[] = [
55
+ { role: "user", text: "I need to transcribe meeting recordings" },
56
+ { role: "assistant", text: "Searching 22,000+ APIs...", search: true },
57
+ {
58
+ role: "assistant",
59
+ text: "Found 4 matches",
60
+ results: [
61
+ { name: "Deepgram", match: "96%", cost: "$0.0043/min" },
62
+ { name: "AssemblyAI", match: "94%", cost: "$0.0065/min" },
63
+ { name: "Rev.ai", match: "91%", cost: "$0.02/min" },
64
+ { name: "Google STT", match: "89%", cost: "$0.006/min" },
65
+ ]
66
+ },
67
+ { role: "assistant", text: "Deepgram recommended", meta: "Best accuracy + pricing", success: true },
68
+ ];
69
+
70
+ const WithoutAPIClaw: WithoutMessage[] = [
71
+ { role: "step", text: "Search for the right API" },
72
+ { role: "step", text: "Open 12 tabs, compare providers" },
73
+ { role: "step", text: "Read documentation for each" },
74
+ { role: "step", text: "Create account, verify email" },
75
+ { role: "step", text: "Set up billing, generate key" },
76
+ { role: "step", text: "Store key securely in .env" },
77
+ { role: "step", text: "Write API integration code" },
78
+ { role: "step", text: "Debug authentication errors" },
79
+ { role: "step", text: "Finally make first API call" },
80
+ ];
81
+
82
+ const examples = [
83
+ { id: "direct", label: "Direct Call", messages: DirectCallExample },
84
+ { id: "open", label: "Open API", messages: OpenAPIExample },
85
+ { id: "discovery", label: "Discovery", messages: DiscoveryExample },
86
+ ];
87
+
88
+ // OpenAI-style logo
89
+ function OpenAILogo({ className }: { className?: string }) {
90
+ return (
91
+ <svg className={className} viewBox="0 0 24 24" fill="currentColor">
92
+ <path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z" />
93
+ </svg>
94
+ );
95
+ }
96
+
97
+ export function PhoneDemo() {
98
+ const [withClaw, setWithClaw] = useState(true);
99
+ const [exampleIndex, setExampleIndex] = useState(0);
100
+ const [visibleMessages, setVisibleMessages] = useState(0);
101
+ const [isPaused, setIsPaused] = useState(false);
102
+ const [completedSteps, setCompletedSteps] = useState<number[]>([]);
103
+
104
+ const currentExample = examples[exampleIndex];
105
+ const messages: Message[] = withClaw ? currentExample.messages : WithoutAPIClaw;
106
+
107
+ // Reset when switching modes or examples
108
+ useEffect(() => {
109
+ setVisibleMessages(0);
110
+ setCompletedSteps([]);
111
+ }, [exampleIndex, withClaw]);
112
+
113
+ // Animate messages appearing
114
+ useEffect(() => {
115
+ if (visibleMessages >= messages.length) return;
116
+
117
+ const delay = withClaw ? 800 : 700;
118
+ const timer = setTimeout(() => {
119
+ setVisibleMessages(v => v + 1);
120
+ // For "Without" mode, mark previous step as completed after a delay
121
+ if (!withClaw && visibleMessages > 0) {
122
+ setTimeout(() => {
123
+ setCompletedSteps(prev => [...prev, visibleMessages - 1]);
124
+ }, 300);
125
+ }
126
+ }, delay);
127
+
128
+ return () => clearTimeout(timer);
129
+ }, [visibleMessages, messages.length, withClaw]);
130
+
131
+ // Mark last step as completed when all visible
132
+ useEffect(() => {
133
+ if (!withClaw && visibleMessages === messages.length && visibleMessages > 0) {
134
+ const timer = setTimeout(() => {
135
+ setCompletedSteps(prev => [...prev, visibleMessages - 1]);
136
+ }, 500);
137
+ return () => clearTimeout(timer);
138
+ }
139
+ }, [visibleMessages, messages.length, withClaw]);
140
+
141
+ // Auto-rotate examples (only when withClaw is true)
142
+ useEffect(() => {
143
+ if (!withClaw || isPaused) return;
144
+ if (visibleMessages < messages.length) return;
145
+
146
+ const timer = setTimeout(() => {
147
+ setExampleIndex((i) => (i + 1) % examples.length);
148
+ }, 3500);
149
+
150
+ return () => clearTimeout(timer);
151
+ }, [visibleMessages, messages.length, withClaw, isPaused]);
152
+
153
+ const handleDotClick = useCallback((index: number) => {
154
+ setExampleIndex(index);
155
+ setIsPaused(true);
156
+ setTimeout(() => setIsPaused(false), 15000);
157
+ }, []);
158
+
159
+ return (
160
+ <div className="w-full max-w-sm mx-auto">
161
+ {/* Toggle */}
162
+ <div className="flex items-center justify-center gap-3 mb-6">
163
+ <button
164
+ onClick={() => setWithClaw(true)}
165
+ className={`px-5 py-2.5 rounded-full font-medium text-sm transition-all duration-200 ${
166
+ withClaw
167
+ ? "bg-zinc-900 text-white shadow-lg"
168
+ : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200"
169
+ }`}
170
+ >
171
+ With APIClaw
172
+ </button>
173
+ <button
174
+ onClick={() => setWithClaw(false)}
175
+ className={`px-5 py-2.5 rounded-full font-medium text-sm transition-all duration-200 ${
176
+ !withClaw
177
+ ? "bg-zinc-900 text-white shadow-lg"
178
+ : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200"
179
+ }`}
180
+ >
181
+ Without
182
+ </button>
183
+ </div>
184
+
185
+ {/* Example indicator (only show when withClaw) */}
186
+ {withClaw && (
187
+ <div className="flex items-center justify-center gap-2 mb-4">
188
+ {examples.map((ex, i) => (
189
+ <button
190
+ key={ex.id}
191
+ onClick={() => handleDotClick(i)}
192
+ className={`px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-200 ${
193
+ i === exampleIndex
194
+ ? "bg-zinc-900 text-white"
195
+ : "bg-zinc-100 text-zinc-500 hover:bg-zinc-200"
196
+ }`}
197
+ >
198
+ {ex.label}
199
+ </button>
200
+ ))}
201
+ </div>
202
+ )}
203
+
204
+ {/* ChatGPT-style Phone Frame */}
205
+ <div className="relative mx-auto" style={{ maxWidth: "340px" }}>
206
+ <div className="relative bg-zinc-900 rounded-[2.5rem] p-2 shadow-2xl">
207
+ <div className="bg-white rounded-[2.2rem] overflow-hidden min-h-[480px] flex flex-col">
208
+ {/* Status bar */}
209
+ <div className="flex items-center justify-between px-6 py-2 text-xs text-zinc-900 font-medium">
210
+ <span>9:41</span>
211
+ <div className="flex items-center gap-1">
212
+ <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7z"/></svg>
213
+ <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M17 4h-3V2h-4v2H7v18h10V4zm-2 16H9V6h6v14z"/></svg>
214
+ </div>
215
+ </div>
216
+
217
+ {/* App Header */}
218
+ <div className="flex items-center justify-between px-4 py-3 border-b border-zinc-100">
219
+ <div className="flex items-center gap-3">
220
+ {withClaw ? (
221
+ <div className="w-8 h-8 rounded-full bg-zinc-900 flex items-center justify-center">
222
+ <OpenAILogo className="w-5 h-5 text-white" />
223
+ </div>
224
+ ) : (
225
+ <div className="w-8 h-8 rounded-full bg-orange-500 flex items-center justify-center">
226
+ <svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
227
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
228
+ </svg>
229
+ </div>
230
+ )}
231
+ <div>
232
+ <div className="text-zinc-900 font-semibold text-sm">
233
+ {withClaw ? "Agent + APIClaw" : "Manual Workflow"}
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ {/* Messages area */}
240
+ <div className="flex-1 overflow-y-auto px-4 py-4 space-y-3 bg-white">
241
+ {messages.slice(0, visibleMessages).map((msg, i) => (
242
+ <div
243
+ key={`${withClaw}-${exampleIndex}-${i}`}
244
+ className="transition-all duration-500 ease-out"
245
+ style={{
246
+ opacity: 1,
247
+ transform: 'translateY(0)',
248
+ animation: 'slideUp 0.4s ease-out'
249
+ }}
250
+ >
251
+ {msg.role === "user" ? (
252
+ <div className="flex justify-end">
253
+ <div className="bg-zinc-100 text-zinc-900 px-4 py-2.5 rounded-2xl rounded-br-md max-w-[85%] text-sm">
254
+ {msg.text}
255
+ </div>
256
+ </div>
257
+ ) : msg.role === "step" ? (
258
+ <div className="flex items-center gap-3 transition-all duration-300">
259
+ <div className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium flex-shrink-0 transition-all duration-300 ${
260
+ completedSteps.includes(i)
261
+ ? "bg-green-500 text-white scale-100"
262
+ : "bg-zinc-100 text-zinc-500"
263
+ }`}>
264
+ {completedSteps.includes(i) ? (
265
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
266
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
267
+ </svg>
268
+ ) : (
269
+ i + 1
270
+ )}
271
+ </div>
272
+ <span className={`text-sm transition-all duration-300 ${
273
+ completedSteps.includes(i) ? "text-zinc-400" : "text-zinc-700"
274
+ }`}>
275
+ {msg.text}
276
+ </span>
277
+ {!completedSteps.includes(i) && i === visibleMessages - 1 && (
278
+ <div className="flex gap-0.5 ml-1">
279
+ <span className="w-1 h-1 bg-zinc-400 rounded-full animate-pulse" />
280
+ <span className="w-1 h-1 bg-zinc-400 rounded-full animate-pulse" style={{ animationDelay: "0.15s" }} />
281
+ <span className="w-1 h-1 bg-zinc-400 rounded-full animate-pulse" style={{ animationDelay: "0.3s" }} />
282
+ </div>
283
+ )}
284
+ </div>
285
+ ) : (
286
+ <div className="flex gap-3">
287
+ <div className="w-7 h-7 rounded-full bg-zinc-900 flex items-center justify-center flex-shrink-0">
288
+ <OpenAILogo className="w-4 h-4 text-white" />
289
+ </div>
290
+
291
+ <div className="flex-1 min-w-0">
292
+ {msg.search && (
293
+ <div className="flex items-center gap-2 text-zinc-700 text-sm py-1 font-medium">
294
+ <svg className="w-4 h-4 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
295
+ <path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
296
+ </svg>
297
+ {msg.text}
298
+ </div>
299
+ )}
300
+ {msg.models && (
301
+ <div className="space-y-2">
302
+ <div className="text-zinc-900 text-sm font-medium">{msg.text}</div>
303
+ <div className="space-y-1.5">
304
+ {msg.models.map((m, j) => (
305
+ <div key={j} className={`flex items-center justify-between text-xs px-3 py-2 rounded-lg transition-all duration-200 ${
306
+ j === 0 ? "bg-zinc-900 text-white" : "bg-zinc-50 text-zinc-600"
307
+ }`}>
308
+ <span className={j === 0 ? "font-medium" : ""}>{m.name}</span>
309
+ <span className={j === 0 ? "text-zinc-300" : "text-zinc-400"}>{m.match}</span>
310
+ </div>
311
+ ))}
312
+ </div>
313
+ </div>
314
+ )}
315
+ {msg.results && (
316
+ <div className="space-y-2">
317
+ <div className="text-zinc-900 text-sm font-medium">{msg.text}</div>
318
+ <div className="space-y-1.5">
319
+ {msg.results.map((r, j) => (
320
+ <div key={j} className={`flex items-center justify-between text-xs px-3 py-2 rounded-lg transition-all duration-200 ${
321
+ j === 0 ? "bg-zinc-900 text-white" : "bg-zinc-50 text-zinc-600"
322
+ }`}>
323
+ <span className={j === 0 ? "font-medium" : ""}>{r.name}</span>
324
+ <div className="flex items-center gap-2">
325
+ <span className={j === 0 ? "text-zinc-300" : "text-zinc-400"}>{r.match}</span>
326
+ <span className={j === 0 ? "text-zinc-500" : "text-zinc-300"}>·</span>
327
+ <span className={j === 0 ? "text-zinc-300" : "text-zinc-400"}>{r.cost}</span>
328
+ </div>
329
+ </div>
330
+ ))}
331
+ </div>
332
+ </div>
333
+ )}
334
+ {!msg.search && !msg.results && !msg.models && (
335
+ <div className="text-zinc-800 text-sm flex items-center gap-2 py-1">
336
+ {msg.success && (
337
+ <svg className="w-4 h-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
338
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
339
+ </svg>
340
+ )}
341
+ {msg.typing && (
342
+ <span className="flex gap-1">
343
+ <span className="w-1.5 h-1.5 bg-zinc-400 rounded-full animate-bounce" />
344
+ <span className="w-1.5 h-1.5 bg-zinc-400 rounded-full animate-bounce" style={{ animationDelay: "0.15s" }} />
345
+ <span className="w-1.5 h-1.5 bg-zinc-400 rounded-full animate-bounce" style={{ animationDelay: "0.3s" }} />
346
+ </span>
347
+ )}
348
+ {msg.text}
349
+ </div>
350
+ )}
351
+ {msg.meta && (
352
+ <div className="text-zinc-400 text-xs mt-0.5">{msg.meta}</div>
353
+ )}
354
+ {msg.image && (
355
+ <div className="mt-2 w-44 aspect-square rounded-xl overflow-hidden shadow-md">
356
+ <Image
357
+ src="/demo-product.jpg"
358
+ alt="Generated product image"
359
+ width={176}
360
+ height={176}
361
+ className="w-full h-full object-cover"
362
+ />
363
+ </div>
364
+ )}
365
+ {msg.currency && (
366
+ <div className="mt-2 bg-zinc-50 border border-zinc-200 rounded-xl p-3 inline-block">
367
+ <div className="flex items-center gap-3">
368
+ <div className="text-2xl font-bold text-zinc-900">10.82</div>
369
+ <div className="text-sm text-zinc-500">
370
+ <div className="font-medium">USD → SEK</div>
371
+ <div className="text-xs text-zinc-400">Live rate</div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+ )}
376
+ </div>
377
+ </div>
378
+ )}
379
+ </div>
380
+ ))}
381
+ </div>
382
+
383
+ {/* Input area */}
384
+ <div className="p-3 border-t border-zinc-100">
385
+ <div className="flex items-center gap-2 bg-zinc-50 border border-zinc-200 rounded-2xl px-4 py-3">
386
+ <input
387
+ type="text"
388
+ placeholder="Message..."
389
+ className="flex-1 bg-transparent text-sm text-zinc-900 placeholder-zinc-400 outline-none"
390
+ readOnly
391
+ />
392
+ <button className="w-7 h-7 bg-zinc-900 rounded-full flex items-center justify-center">
393
+ <svg className="w-3.5 h-3.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
394
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5 10l7-7m0 0l7 7m-7-7v18" />
395
+ </svg>
396
+ </button>
397
+ </div>
398
+ </div>
399
+
400
+ {/* Home indicator */}
401
+ <div className="flex justify-center pb-2">
402
+ <div className="w-32 h-1 bg-zinc-900 rounded-full" />
403
+ </div>
404
+ </div>
405
+ </div>
406
+ </div>
407
+
408
+ {/* CSS for animations */}
409
+ <style jsx>{`
410
+ @keyframes slideUp {
411
+ from {
412
+ opacity: 0;
413
+ transform: translateY(10px);
414
+ }
415
+ to {
416
+ opacity: 1;
417
+ transform: translateY(0);
418
+ }
419
+ }
420
+ `}</style>
421
+ </div>
422
+ );
423
+ }
@@ -0,0 +1 @@
1
+ export { PhoneDemo } from './PhoneDemo';
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "apiCount": 22392,
3
3
  "openApiCount": 1636,
4
- "directCallCount": 10,
4
+ "directCallCount": 18,
5
5
  "categoryCount": 13,
6
6
  "lastUpdated": "2026-02-27T09:10:41.344767",
7
- "generatedAt": "2026-03-01T02:24:02.189Z",
7
+ "generatedAt": "2026-03-01T16:05:56.209Z",
8
8
  "categoryBreakdown": {
9
9
  "Finance": 1179,
10
10
  "Auth & Security": 491,
@@ -28,8 +28,18 @@ const config: Config = {
28
28
  'tighter': '-0.03em',
29
29
  'widest': '0.15em',
30
30
  },
31
+ animation: {
32
+ 'fade-in': 'fadeIn 0.3s ease-out forwards',
33
+ },
34
+ keyframes: {
35
+ fadeIn: {
36
+ '0%': { opacity: '0', transform: 'translateY(8px)' },
37
+ '100%': { opacity: '1', transform: 'translateY(0)' },
38
+ },
39
+ },
31
40
  },
32
41
  },
33
42
  plugins: [],
34
43
  };
44
+
35
45
  export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nordsym/apiclaw",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "Agent-native API discovery and Direct Call execution via MCP",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/bin.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * - setup/doctor/restore/uninstall → Run CLI
7
7
  */
8
8
 
9
- const cliCommands = ['setup', 'doctor', 'restore', 'uninstall', 'help', '--help', '-h', '--version', '-V'];
9
+ const cliCommands = ['setup', 'mcp-install', 'doctor', 'restore', 'uninstall', 'help', '--help', '-h', '--version', '-V'];
10
10
 
11
11
  const firstArg = process.argv[2];
12
12