@nordsym/apiclaw 1.2.2 → 1.2.3
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.
- package/AGENTS.md +50 -33
- package/README.md +22 -12
- package/SOUL.md +60 -19
- package/STATUS.md +91 -169
- package/convex/_generated/api.d.ts +6 -0
- package/convex/directCall.ts +598 -0
- package/convex/providers.ts +341 -26
- package/convex/schema.ts +87 -0
- package/convex/usage.ts +260 -0
- package/convex/waitlist.ts +55 -0
- package/data/combined-02-26.json +22102 -0
- package/data/night-expansion-02-26-06-batch2.json +1898 -0
- package/data/night-expansion-02-26-06-batch3.json +1410 -0
- package/data/night-expansion-02-26-06.json +3146 -0
- package/data/night-expansion-02-26-full.json +9726 -0
- package/data/night-expansion-02-26-v2.json +330 -0
- package/data/night-expansion-02-26.json +171 -0
- package/dist/crypto.d.ts +7 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +67 -0
- package/dist/crypto.js.map +1 -0
- package/dist/execute-dynamic.d.ts +116 -0
- package/dist/execute-dynamic.d.ts.map +1 -0
- package/dist/execute-dynamic.js +456 -0
- package/dist/execute-dynamic.js.map +1 -0
- package/dist/execute.d.ts +2 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +35 -5
- package/dist/execute.js.map +1 -1
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/dist/registry/apis.json +2081 -3
- package/docs/PRD-customer-key-passthrough.md +184 -0
- package/landing/public/badges/available-on-apiclaw.svg +14 -0
- package/landing/scripts/generate-stats.js +75 -4
- package/landing/src/app/admin/page.tsx +1 -1
- package/landing/src/app/api/auth/magic-link/route.ts +1 -1
- package/landing/src/app/api/auth/session/route.ts +1 -1
- package/landing/src/app/api/auth/verify/route.ts +1 -1
- package/landing/src/app/api/og/route.tsx +5 -3
- package/landing/src/app/docs/page.tsx +5 -4
- package/landing/src/app/earn/page.tsx +14 -11
- package/landing/src/app/globals.css +16 -15
- package/landing/src/app/layout.tsx +2 -2
- package/landing/src/app/page.tsx +425 -254
- package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +600 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +583 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +301 -0
- package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +659 -0
- package/landing/src/app/providers/dashboard/[apiId]/page.tsx +381 -0
- package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +418 -0
- package/landing/src/app/providers/dashboard/layout.tsx +292 -0
- package/landing/src/app/providers/dashboard/page.tsx +353 -290
- package/landing/src/app/providers/register/page.tsx +87 -10
- package/landing/src/components/AiClientDropdown.tsx +85 -0
- package/landing/src/components/ConfigHelperModal.tsx +113 -0
- package/landing/src/components/HeroTabs.tsx +187 -0
- package/landing/src/components/ShareIntegrationModal.tsx +198 -0
- package/landing/src/hooks/useDashboardData.ts +53 -1
- package/landing/src/lib/apis.json +46554 -174
- package/landing/src/lib/convex-client.ts +22 -3
- package/landing/src/lib/stats.json +4 -4
- package/landing/tsconfig.tsbuildinfo +1 -1
- package/night-expansion-02-26-06-batch2.py +368 -0
- package/night-expansion-02-26-06-batch3.py +299 -0
- package/night-expansion-02-26-06.py +756 -0
- package/package.json +1 -1
- package/scripts/bulk-add-public-apis-v2.py +418 -0
- package/scripts/night-expansion-02-26-v2.py +296 -0
- package/scripts/night-expansion-02-26.py +890 -0
- package/scripts/seed-complete-api.js +181 -0
- package/scripts/seed-demo-api.sh +44 -0
- package/src/crypto.ts +75 -0
- package/src/execute-dynamic.ts +589 -0
- package/src/execute.ts +41 -5
- package/src/index.ts +38 -4
- package/src/registry/apis.json +2081 -3
|
@@ -62,6 +62,7 @@ export default function RegisterPage() {
|
|
|
62
62
|
const [isComplete, setIsComplete] = useState(false);
|
|
63
63
|
const [parseStatus, setParseStatus] = useState<'idle' | 'parsing' | 'success' | 'error'>('idle');
|
|
64
64
|
const [error, setError] = useState<string | null>(null);
|
|
65
|
+
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
|
65
66
|
|
|
66
67
|
const [formData, setFormData] = useState<FormData>({
|
|
67
68
|
providerName: "",
|
|
@@ -76,6 +77,29 @@ export default function RegisterPage() {
|
|
|
76
77
|
pricingNotes: "",
|
|
77
78
|
});
|
|
78
79
|
|
|
80
|
+
// Check if user is logged in and pre-fill provider info
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const token = localStorage.getItem('apiclaw_session');
|
|
83
|
+
const providerData = localStorage.getItem('apiclaw_provider');
|
|
84
|
+
|
|
85
|
+
if (token && providerData) {
|
|
86
|
+
try {
|
|
87
|
+
const provider = JSON.parse(providerData);
|
|
88
|
+
setFormData(prev => ({
|
|
89
|
+
...prev,
|
|
90
|
+
providerName: provider.name || prev.providerName,
|
|
91
|
+
email: provider.email || prev.email,
|
|
92
|
+
website: provider.website || prev.website,
|
|
93
|
+
}));
|
|
94
|
+
setIsLoggedIn(true);
|
|
95
|
+
// Skip to step 2 if logged in
|
|
96
|
+
setStep(2);
|
|
97
|
+
} catch {
|
|
98
|
+
// Invalid provider data, continue as guest
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
79
103
|
useEffect(() => {
|
|
80
104
|
const saved = localStorage.getItem('theme');
|
|
81
105
|
const prefersDark = saved ? saved === 'dark' : true;
|
|
@@ -175,8 +199,41 @@ export default function RegisterPage() {
|
|
|
175
199
|
setError(null);
|
|
176
200
|
|
|
177
201
|
try {
|
|
178
|
-
|
|
179
|
-
|
|
202
|
+
const token = localStorage.getItem('apiclaw_session');
|
|
203
|
+
|
|
204
|
+
// If logged in, add API to existing account
|
|
205
|
+
if (token && isLoggedIn) {
|
|
206
|
+
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud'}/api/mutation`, {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: { 'Content-Type': 'application/json' },
|
|
209
|
+
body: JSON.stringify({
|
|
210
|
+
path: 'providers:addAPI',
|
|
211
|
+
args: {
|
|
212
|
+
token,
|
|
213
|
+
api: {
|
|
214
|
+
name: formData.apiName,
|
|
215
|
+
description: formData.description,
|
|
216
|
+
category: formData.category,
|
|
217
|
+
openApiUrl: formData.openApiUrl || undefined,
|
|
218
|
+
docsUrl: formData.docsUrl || undefined,
|
|
219
|
+
pricingModel: formData.pricingModel,
|
|
220
|
+
pricingNotes: formData.pricingNotes || undefined,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
const errorData = await response.json().catch(() => ({}));
|
|
228
|
+
throw new Error(errorData.message || 'Failed to add API');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setIsComplete(true);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Submit to Convex (new provider)
|
|
236
|
+
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud'}/api/mutation`, {
|
|
180
237
|
method: 'POST',
|
|
181
238
|
headers: { 'Content-Type': 'application/json' },
|
|
182
239
|
body: JSON.stringify({
|
|
@@ -205,6 +262,12 @@ export default function RegisterPage() {
|
|
|
205
262
|
throw new Error(errorData.message || 'Submission failed');
|
|
206
263
|
}
|
|
207
264
|
|
|
265
|
+
// Parse response and save session token for auto-login
|
|
266
|
+
const result = await response.json();
|
|
267
|
+
if (result.value?.sessionToken) {
|
|
268
|
+
localStorage.setItem('apiclaw_session', result.value.sessionToken);
|
|
269
|
+
}
|
|
270
|
+
|
|
208
271
|
// Send confirmation email via Symbot SMTP
|
|
209
272
|
await fetch('https://nordsym.app.n8n.cloud/webhook/symbot-gmail', {
|
|
210
273
|
method: 'POST',
|
|
@@ -300,6 +363,11 @@ export default function RegisterPage() {
|
|
|
300
363
|
<span className="font-bold text-lg tracking-tight">APIClaw</span>
|
|
301
364
|
</Link>
|
|
302
365
|
<div className="flex items-center gap-3">
|
|
366
|
+
{isLoggedIn && (
|
|
367
|
+
<span className="text-sm text-text-muted hidden sm:block">
|
|
368
|
+
Logged in as <strong className="text-text-primary">{formData.providerName}</strong>
|
|
369
|
+
</span>
|
|
370
|
+
)}
|
|
303
371
|
<button
|
|
304
372
|
onClick={toggleTheme}
|
|
305
373
|
className="p-2 rounded-lg hover:bg-[var(--surface)] transition"
|
|
@@ -307,14 +375,23 @@ export default function RegisterPage() {
|
|
|
307
375
|
>
|
|
308
376
|
{isDark ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
|
309
377
|
</button>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
378
|
+
{isLoggedIn ? (
|
|
379
|
+
<Link
|
|
380
|
+
href="/providers/dashboard"
|
|
381
|
+
className="btn-secondary !py-2 !px-4 text-sm"
|
|
382
|
+
>
|
|
383
|
+
Dashboard
|
|
384
|
+
</Link>
|
|
385
|
+
) : (
|
|
386
|
+
<a
|
|
387
|
+
href="https://github.com/nordsym/apiclaw"
|
|
388
|
+
target="_blank"
|
|
389
|
+
rel="noopener noreferrer"
|
|
390
|
+
className="btn-secondary !py-2 !px-4 text-sm"
|
|
391
|
+
>
|
|
392
|
+
<Github className="w-4 h-4" />
|
|
393
|
+
</a>
|
|
394
|
+
)}
|
|
318
395
|
</div>
|
|
319
396
|
</div>
|
|
320
397
|
</header>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
|
+
import { useState, useRef, useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
export type AiClient = "chatgpt" | "other";
|
|
7
|
+
|
|
8
|
+
interface AiClientOption {
|
|
9
|
+
value: AiClient;
|
|
10
|
+
label: string;
|
|
11
|
+
configPath: string;
|
|
12
|
+
isGui?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const clients: AiClientOption[] = [
|
|
16
|
+
{
|
|
17
|
+
value: "other",
|
|
18
|
+
label: "Claude, Cursor, Cline & others",
|
|
19
|
+
configPath: "Add to your MCP config file",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
value: "chatgpt",
|
|
23
|
+
label: "ChatGPT",
|
|
24
|
+
configPath: "Settings → Connections → Add MCP",
|
|
25
|
+
isGui: true,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
value: AiClient;
|
|
31
|
+
onChange: (client: AiClient) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function AiClientDropdown({ value, onChange }: Props) {
|
|
35
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
36
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
|
|
38
|
+
const selected = clients.find((c) => c.value === value) || clients[0];
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
function handleClickOutside(event: MouseEvent) {
|
|
42
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
43
|
+
setIsOpen(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
47
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="relative" ref={dropdownRef}>
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
54
|
+
className="flex items-center justify-between gap-3 w-full sm:w-64 px-4 py-3 rounded-xl bg-surface border border-border hover:border-accent/50 transition-colors text-left"
|
|
55
|
+
>
|
|
56
|
+
<span className="font-medium">{selected.label}</span>
|
|
57
|
+
<ChevronDown className={`w-4 h-4 text-text-muted transition-transform ${isOpen ? "rotate-180" : ""}`} />
|
|
58
|
+
</button>
|
|
59
|
+
|
|
60
|
+
{isOpen && (
|
|
61
|
+
<div className="absolute z-50 mt-2 w-full sm:w-64 rounded-xl bg-surface-elevated border border-border shadow-xl overflow-hidden">
|
|
62
|
+
{clients.map((client) => (
|
|
63
|
+
<button
|
|
64
|
+
key={client.value}
|
|
65
|
+
onClick={() => {
|
|
66
|
+
onChange(client.value);
|
|
67
|
+
setIsOpen(false);
|
|
68
|
+
}}
|
|
69
|
+
className={`w-full px-4 py-3 text-left hover:bg-surface transition-colors ${
|
|
70
|
+
client.value === value ? "bg-accent/10 text-accent" : ""
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
<div className="font-medium">{client.label}</div>
|
|
74
|
+
<div className="text-xs text-text-muted truncate">{client.configPath}</div>
|
|
75
|
+
</button>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getClientConfig(client: AiClient): AiClientOption {
|
|
84
|
+
return clients.find((c) => c.value === client) || clients[0];
|
|
85
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { X, ExternalLink, Folder } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const configLocations = [
|
|
11
|
+
{
|
|
12
|
+
name: "Claude Desktop",
|
|
13
|
+
paths: [
|
|
14
|
+
{ os: "Mac", path: "~/Library/Application Support/Claude/", file: "claude_desktop_config.json" },
|
|
15
|
+
{ os: "Windows", path: "%APPDATA%\\Claude\\", file: "claude_desktop_config.json" },
|
|
16
|
+
{ os: "Linux", path: "~/.config/Claude/", file: "claude_desktop_config.json" },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "ChatGPT",
|
|
21
|
+
paths: [
|
|
22
|
+
{ os: "All", path: "Settings → Connections → Add MCP Server", file: "" },
|
|
23
|
+
],
|
|
24
|
+
isGui: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "Cursor",
|
|
28
|
+
paths: [
|
|
29
|
+
{ os: "All", path: "~/.cursor/mcp.json", file: "" },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "Cline",
|
|
34
|
+
paths: [
|
|
35
|
+
{ os: "All", path: "~/.cline/mcp_config.json", file: "" },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export function ConfigHelperModal({ isOpen, onClose }: Props) {
|
|
41
|
+
if (!isOpen) return null;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
|
|
46
|
+
onClick={onClose}
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
className="bg-background border border-border rounded-2xl shadow-2xl max-w-lg w-full max-h-[85vh] overflow-hidden"
|
|
50
|
+
onClick={(e) => e.stopPropagation()}
|
|
51
|
+
>
|
|
52
|
+
{/* Header */}
|
|
53
|
+
<div className="p-4 sm:p-6 border-b border-border">
|
|
54
|
+
<div className="flex items-center justify-between">
|
|
55
|
+
<h3 className="text-lg sm:text-xl font-bold flex items-center gap-2">
|
|
56
|
+
<Folder className="w-5 h-5 text-accent" />
|
|
57
|
+
Config File Locations
|
|
58
|
+
</h3>
|
|
59
|
+
<button
|
|
60
|
+
onClick={onClose}
|
|
61
|
+
className="p-2 hover:bg-surface rounded-lg transition-colors"
|
|
62
|
+
>
|
|
63
|
+
<X className="w-5 h-5" />
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Content */}
|
|
69
|
+
<div className="p-4 sm:p-6 space-y-3 max-h-[60vh] overflow-y-auto">
|
|
70
|
+
{configLocations.map((client, i) => (
|
|
71
|
+
<div key={i} className="p-3 sm:p-4 rounded-xl bg-surface border border-border">
|
|
72
|
+
<div className="flex items-center gap-2 mb-2">
|
|
73
|
+
<span className="font-semibold text-sm sm:text-base">{client.name}</span>
|
|
74
|
+
{client.isGui && (
|
|
75
|
+
<span className="text-xs px-2 py-0.5 rounded-full bg-accent/20 text-accent">GUI</span>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className="space-y-1.5">
|
|
80
|
+
{client.paths.map((p, j) => (
|
|
81
|
+
<div key={j} className="text-xs sm:text-sm">
|
|
82
|
+
{p.os !== "All" && (
|
|
83
|
+
<span className="text-text-muted mr-1">{p.os}:</span>
|
|
84
|
+
)}
|
|
85
|
+
<code className="text-text-secondary bg-surface-elevated px-1.5 py-0.5 rounded text-xs break-all">
|
|
86
|
+
{p.path}{p.file && ` ${p.file}`}
|
|
87
|
+
</code>
|
|
88
|
+
</div>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Footer */}
|
|
96
|
+
<div className="p-4 sm:p-6 border-t border-border bg-surface/50">
|
|
97
|
+
<p className="text-xs sm:text-sm text-text-muted mb-3 text-center">
|
|
98
|
+
After adding config, restart your AI app.
|
|
99
|
+
</p>
|
|
100
|
+
<a
|
|
101
|
+
href="https://github.com/nordsym/apiclaw#installation"
|
|
102
|
+
target="_blank"
|
|
103
|
+
rel="noopener noreferrer"
|
|
104
|
+
className="btn-secondary w-full justify-center text-sm"
|
|
105
|
+
>
|
|
106
|
+
<span>Full Setup Guide</span>
|
|
107
|
+
<ExternalLink className="w-4 h-4" />
|
|
108
|
+
</a>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Copy, Check, HelpCircle, ArrowRight, Users, Zap, BarChart3, Gift } from "lucide-react";
|
|
5
|
+
import { AiClientDropdown, getClientConfig, type AiClient } from "./AiClientDropdown";
|
|
6
|
+
import { ConfigHelperModal } from "./ConfigHelperModal";
|
|
7
|
+
|
|
8
|
+
export function HeroTabs() {
|
|
9
|
+
const [activeTab, setActiveTab] = useState<"connect" | "add">("connect");
|
|
10
|
+
const [selectedClient, setSelectedClient] = useState<AiClient>("other");
|
|
11
|
+
const [showConfigHelper, setShowConfigHelper] = useState(false);
|
|
12
|
+
const [copiedConfig, setCopiedConfig] = useState(false);
|
|
13
|
+
const [copiedTerminal, setCopiedTerminal] = useState(false);
|
|
14
|
+
|
|
15
|
+
// Config snippets
|
|
16
|
+
const jsonConfig = JSON.stringify({
|
|
17
|
+
mcpServers: {
|
|
18
|
+
apiclaw: {
|
|
19
|
+
command: "npx",
|
|
20
|
+
args: ["@nordsym/apiclaw"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, null, 2);
|
|
24
|
+
|
|
25
|
+
const chatGptInstructions = `1. Open ChatGPT Settings
|
|
26
|
+
2. Go to Connections → Add MCP Server
|
|
27
|
+
3. Enter:
|
|
28
|
+
• Name: apiclaw
|
|
29
|
+
• Command: npx @nordsym/apiclaw
|
|
30
|
+
4. Save and restart ChatGPT`;
|
|
31
|
+
|
|
32
|
+
const configSnippetJson = selectedClient === "chatgpt" ? chatGptInstructions : jsonConfig;
|
|
33
|
+
|
|
34
|
+
const terminalCommand = "npx @nordsym/apiclaw";
|
|
35
|
+
|
|
36
|
+
const copyConfig = () => {
|
|
37
|
+
navigator.clipboard.writeText(configSnippetJson);
|
|
38
|
+
setCopiedConfig(true);
|
|
39
|
+
setTimeout(() => setCopiedConfig(false), 2000);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const copyTerminal = () => {
|
|
43
|
+
navigator.clipboard.writeText(terminalCommand);
|
|
44
|
+
setCopiedTerminal(true);
|
|
45
|
+
setTimeout(() => setCopiedTerminal(false), 2000);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const clientConfig = getClientConfig(selectedClient);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<div className="w-full max-w-2xl mx-auto lg:mx-0">
|
|
53
|
+
{/* Tab Buttons */}
|
|
54
|
+
<div className="flex gap-2 mb-6">
|
|
55
|
+
<button
|
|
56
|
+
onClick={() => setActiveTab("connect")}
|
|
57
|
+
className={`flex-1 py-3 px-4 rounded-xl font-medium transition-all ${
|
|
58
|
+
activeTab === "connect"
|
|
59
|
+
? "bg-accent text-white shadow-lg shadow-accent/25"
|
|
60
|
+
: "bg-surface border border-border hover:border-accent/50"
|
|
61
|
+
}`}
|
|
62
|
+
>
|
|
63
|
+
Connect Your Agent
|
|
64
|
+
</button>
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => setActiveTab("add")}
|
|
67
|
+
className={`flex-1 py-3 px-4 rounded-xl font-medium transition-all ${
|
|
68
|
+
activeTab === "add"
|
|
69
|
+
? "bg-accent text-white shadow-lg shadow-accent/25"
|
|
70
|
+
: "bg-surface border border-border hover:border-accent/50"
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
Add Your API
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Tab Content */}
|
|
78
|
+
<div className="rounded-2xl bg-surface-elevated border border-border p-6 shadow-xl">
|
|
79
|
+
{activeTab === "connect" && (
|
|
80
|
+
<div className="space-y-6">
|
|
81
|
+
{/* Client Selector */}
|
|
82
|
+
<div>
|
|
83
|
+
<label className="block text-sm text-text-muted mb-2">Select your AI:</label>
|
|
84
|
+
<AiClientDropdown value={selectedClient} onChange={setSelectedClient} />
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* Config Snippet */}
|
|
88
|
+
<div>
|
|
89
|
+
<label className="block text-sm text-text-muted mb-2">
|
|
90
|
+
{clientConfig.isGui ? "Setup instructions:" : "Add to your config:"}
|
|
91
|
+
</label>
|
|
92
|
+
<div className="code-preview">
|
|
93
|
+
<div className="code-preview-header">
|
|
94
|
+
{clientConfig.isGui ? "Instructions" : clientConfig.configPath.split("/").pop()}
|
|
95
|
+
</div>
|
|
96
|
+
<div className="code-preview-body">
|
|
97
|
+
<pre className="text-sm whitespace-pre-wrap text-text-secondary">{configSnippetJson}</pre>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="flex flex-wrap items-center gap-3 mt-3">
|
|
102
|
+
<button onClick={copyConfig} className="btn-primary !py-2 !px-4 text-sm">
|
|
103
|
+
{copiedConfig ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
|
104
|
+
{copiedConfig ? "Copied!" : "Copy Config"}
|
|
105
|
+
</button>
|
|
106
|
+
<button
|
|
107
|
+
onClick={() => setShowConfigHelper(true)}
|
|
108
|
+
className="btn-ghost !py-2 !px-4 text-sm"
|
|
109
|
+
>
|
|
110
|
+
<HelpCircle className="w-4 h-4" />
|
|
111
|
+
Where's my config file?
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Divider */}
|
|
117
|
+
<div className="flex items-center gap-4">
|
|
118
|
+
<div className="flex-1 h-px bg-border" />
|
|
119
|
+
<span className="text-sm text-text-muted">or run directly</span>
|
|
120
|
+
<div className="flex-1 h-px bg-border" />
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Terminal Command */}
|
|
124
|
+
<div>
|
|
125
|
+
<div className="code-preview">
|
|
126
|
+
<div className="code-preview-header">terminal</div>
|
|
127
|
+
<div className="code-preview-body">
|
|
128
|
+
<pre className="text-sm">
|
|
129
|
+
<span className="text-green-400">$</span> <span className="text-blue-400">npx</span> @nordsym/apiclaw
|
|
130
|
+
</pre>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="flex items-center gap-3 mt-3">
|
|
134
|
+
<button onClick={copyTerminal} className="btn-ghost !py-2 !px-4 text-sm">
|
|
135
|
+
{copiedTerminal ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
|
136
|
+
{copiedTerminal ? "Copied!" : "Copy"}
|
|
137
|
+
</button>
|
|
138
|
+
<span className="text-xs text-text-muted">← Run in terminal to test</span>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{activeTab === "add" && (
|
|
145
|
+
<div className="space-y-6">
|
|
146
|
+
<div>
|
|
147
|
+
<h3 className="text-xl font-bold mb-2">Make your API available to AI agents</h3>
|
|
148
|
+
<p className="text-text-secondary">
|
|
149
|
+
Self-service onboarding. Live in minutes. Free to list.
|
|
150
|
+
</p>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<ul className="space-y-3">
|
|
154
|
+
<li className="flex items-center gap-3 text-text-secondary">
|
|
155
|
+
<Users className="w-5 h-5 text-accent flex-shrink-0" />
|
|
156
|
+
<span>Reach thousands of AI agents</span>
|
|
157
|
+
</li>
|
|
158
|
+
<li className="flex items-center gap-3 text-text-secondary">
|
|
159
|
+
<Zap className="w-5 h-5 text-accent flex-shrink-0" />
|
|
160
|
+
<span>No code changes required</span>
|
|
161
|
+
</li>
|
|
162
|
+
<li className="flex items-center gap-3 text-text-secondary">
|
|
163
|
+
<BarChart3 className="w-5 h-5 text-accent flex-shrink-0" />
|
|
164
|
+
<span>Analytics & usage insights</span>
|
|
165
|
+
</li>
|
|
166
|
+
<li className="flex items-center gap-3 text-text-secondary">
|
|
167
|
+
<Gift className="w-5 h-5 text-accent flex-shrink-0" />
|
|
168
|
+
<span>Free to list</span>
|
|
169
|
+
</li>
|
|
170
|
+
</ul>
|
|
171
|
+
|
|
172
|
+
<a
|
|
173
|
+
href="/providers/dashboard"
|
|
174
|
+
className="btn-primary w-full justify-center"
|
|
175
|
+
>
|
|
176
|
+
Open Provider Dashboard
|
|
177
|
+
<ArrowRight className="w-4 h-4" />
|
|
178
|
+
</a>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<ConfigHelperModal isOpen={showConfigHelper} onClose={() => setShowConfigHelper(false)} />
|
|
185
|
+
</>
|
|
186
|
+
);
|
|
187
|
+
}
|