apteva 0.2.6 → 0.2.8
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/dist/App.hzbfeg94.js +217 -0
- package/dist/index.html +3 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/auth/index.ts +386 -0
- package/src/auth/middleware.ts +183 -0
- package/src/binary.ts +19 -1
- package/src/db.ts +570 -32
- package/src/routes/api.ts +913 -38
- package/src/routes/auth.ts +242 -0
- package/src/server.ts +60 -8
- package/src/web/App.tsx +61 -19
- package/src/web/components/agents/AgentCard.tsx +30 -41
- package/src/web/components/agents/AgentPanel.tsx +751 -11
- package/src/web/components/agents/AgentsView.tsx +81 -9
- package/src/web/components/agents/CreateAgentModal.tsx +28 -1
- package/src/web/components/auth/CreateAccountStep.tsx +176 -0
- package/src/web/components/auth/LoginPage.tsx +91 -0
- package/src/web/components/auth/index.ts +2 -0
- package/src/web/components/common/Icons.tsx +48 -0
- package/src/web/components/common/Modal.tsx +1 -1
- package/src/web/components/dashboard/Dashboard.tsx +91 -31
- package/src/web/components/index.ts +3 -0
- package/src/web/components/layout/Header.tsx +145 -15
- package/src/web/components/layout/Sidebar.tsx +81 -43
- package/src/web/components/mcp/McpPage.tsx +261 -32
- package/src/web/components/onboarding/OnboardingWizard.tsx +64 -8
- package/src/web/components/settings/SettingsPage.tsx +404 -18
- package/src/web/components/tasks/TasksPage.tsx +21 -19
- package/src/web/components/telemetry/TelemetryPage.tsx +271 -81
- package/src/web/context/AuthContext.tsx +230 -0
- package/src/web/context/ProjectContext.tsx +182 -0
- package/src/web/context/TelemetryContext.tsx +98 -76
- package/src/web/context/index.ts +5 -0
- package/src/web/hooks/useAgents.ts +18 -6
- package/src/web/hooks/useOnboarding.ts +20 -4
- package/src/web/hooks/useProviders.ts +15 -5
- package/src/web/icon.png +0 -0
- package/src/web/styles.css +12 -0
- package/src/web/types.ts +6 -0
- package/dist/App.0mzj9cz9.js +0 -213
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { McpIcon } from "../common/Icons";
|
|
3
|
+
import { useAuth } from "../../context";
|
|
3
4
|
import type { McpTool, McpToolCallResult } from "../../types";
|
|
4
5
|
|
|
5
6
|
interface McpServer {
|
|
@@ -16,24 +17,28 @@ interface McpServer {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
interface RegistryServer {
|
|
20
|
+
id: string;
|
|
19
21
|
name: string;
|
|
22
|
+
fullName: string;
|
|
20
23
|
description: string;
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
version?: string;
|
|
25
|
+
repository?: string;
|
|
23
26
|
npmPackage: string | null;
|
|
24
|
-
|
|
27
|
+
remoteUrl: string | null;
|
|
28
|
+
transport: string;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export function McpPage() {
|
|
32
|
+
const { authFetch } = useAuth();
|
|
28
33
|
const [servers, setServers] = useState<McpServer[]>([]);
|
|
29
34
|
const [loading, setLoading] = useState(true);
|
|
30
35
|
const [showAdd, setShowAdd] = useState(false);
|
|
31
36
|
const [selectedServer, setSelectedServer] = useState<McpServer | null>(null);
|
|
32
|
-
const [activeTab, setActiveTab] = useState<"servers" | "registry">("servers");
|
|
37
|
+
const [activeTab, setActiveTab] = useState<"servers" | "hosted" | "registry">("servers");
|
|
33
38
|
|
|
34
39
|
const fetchServers = async () => {
|
|
35
40
|
try {
|
|
36
|
-
const res = await
|
|
41
|
+
const res = await authFetch("/api/mcp/servers");
|
|
37
42
|
const data = await res.json();
|
|
38
43
|
setServers(data.servers || []);
|
|
39
44
|
} catch (e) {
|
|
@@ -44,11 +49,11 @@ export function McpPage() {
|
|
|
44
49
|
|
|
45
50
|
useEffect(() => {
|
|
46
51
|
fetchServers();
|
|
47
|
-
}, []);
|
|
52
|
+
}, [authFetch]);
|
|
48
53
|
|
|
49
54
|
const startServer = async (id: string) => {
|
|
50
55
|
try {
|
|
51
|
-
await
|
|
56
|
+
await authFetch(`/api/mcp/servers/${id}/start`, { method: "POST" });
|
|
52
57
|
fetchServers();
|
|
53
58
|
} catch (e) {
|
|
54
59
|
console.error("Failed to start server:", e);
|
|
@@ -57,7 +62,7 @@ export function McpPage() {
|
|
|
57
62
|
|
|
58
63
|
const stopServer = async (id: string) => {
|
|
59
64
|
try {
|
|
60
|
-
await
|
|
65
|
+
await authFetch(`/api/mcp/servers/${id}/stop`, { method: "POST" });
|
|
61
66
|
fetchServers();
|
|
62
67
|
} catch (e) {
|
|
63
68
|
console.error("Failed to stop server:", e);
|
|
@@ -67,7 +72,7 @@ export function McpPage() {
|
|
|
67
72
|
const deleteServer = async (id: string) => {
|
|
68
73
|
if (!confirm("Delete this MCP server?")) return;
|
|
69
74
|
try {
|
|
70
|
-
await
|
|
75
|
+
await authFetch(`/api/mcp/servers/${id}`, { method: "DELETE" });
|
|
71
76
|
if (selectedServer?.id === id) {
|
|
72
77
|
setSelectedServer(null);
|
|
73
78
|
}
|
|
@@ -110,6 +115,16 @@ export function McpPage() {
|
|
|
110
115
|
>
|
|
111
116
|
My Servers
|
|
112
117
|
</button>
|
|
118
|
+
<button
|
|
119
|
+
onClick={() => setActiveTab("hosted")}
|
|
120
|
+
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
121
|
+
activeTab === "hosted"
|
|
122
|
+
? "bg-[#1a1a1a] text-white"
|
|
123
|
+
: "text-[#666] hover:text-[#888]"
|
|
124
|
+
}`}
|
|
125
|
+
>
|
|
126
|
+
Hosted Services
|
|
127
|
+
</button>
|
|
113
128
|
<button
|
|
114
129
|
onClick={() => setActiveTab("registry")}
|
|
115
130
|
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
@@ -188,6 +203,11 @@ export function McpPage() {
|
|
|
188
203
|
</>
|
|
189
204
|
)}
|
|
190
205
|
|
|
206
|
+
{/* Hosted Services Tab */}
|
|
207
|
+
{activeTab === "hosted" && (
|
|
208
|
+
<HostedServices />
|
|
209
|
+
)}
|
|
210
|
+
|
|
191
211
|
{/* Browse Registry Tab */}
|
|
192
212
|
{activeTab === "registry" && (
|
|
193
213
|
<RegistryBrowser
|
|
@@ -313,6 +333,7 @@ function ToolsPanel({
|
|
|
313
333
|
server: McpServer;
|
|
314
334
|
onClose: () => void;
|
|
315
335
|
}) {
|
|
336
|
+
const { authFetch } = useAuth();
|
|
316
337
|
const [tools, setTools] = useState<McpTool[]>([]);
|
|
317
338
|
const [serverInfo, setServerInfo] = useState<{ name: string; version: string } | null>(null);
|
|
318
339
|
const [loading, setLoading] = useState(true);
|
|
@@ -324,7 +345,7 @@ function ToolsPanel({
|
|
|
324
345
|
setLoading(true);
|
|
325
346
|
setError(null);
|
|
326
347
|
try {
|
|
327
|
-
const res = await
|
|
348
|
+
const res = await authFetch(`/api/mcp/servers/${server.id}/tools`);
|
|
328
349
|
const data = await res.json();
|
|
329
350
|
if (!res.ok) {
|
|
330
351
|
setError(data.error || "Failed to fetch tools");
|
|
@@ -340,7 +361,7 @@ function ToolsPanel({
|
|
|
340
361
|
};
|
|
341
362
|
|
|
342
363
|
fetchTools();
|
|
343
|
-
}, [server.id]);
|
|
364
|
+
}, [server.id, authFetch]);
|
|
344
365
|
|
|
345
366
|
return (
|
|
346
367
|
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg overflow-hidden">
|
|
@@ -414,6 +435,7 @@ function ToolTester({
|
|
|
414
435
|
tool: McpTool;
|
|
415
436
|
onBack: () => void;
|
|
416
437
|
}) {
|
|
438
|
+
const { authFetch } = useAuth();
|
|
417
439
|
const [args, setArgs] = useState<string>("{}");
|
|
418
440
|
const [result, setResult] = useState<McpToolCallResult | null>(null);
|
|
419
441
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -451,7 +473,7 @@ function ToolTester({
|
|
|
451
473
|
|
|
452
474
|
try {
|
|
453
475
|
const parsedArgs = JSON.parse(args);
|
|
454
|
-
const res = await
|
|
476
|
+
const res = await authFetch(`/api/mcp/servers/${serverId}/tools/${encodeURIComponent(tool.name)}/call`, {
|
|
455
477
|
method: "POST",
|
|
456
478
|
headers: { "Content-Type": "application/json" },
|
|
457
479
|
body: JSON.stringify({ arguments: parsedArgs }),
|
|
@@ -564,6 +586,7 @@ function RegistryBrowser({
|
|
|
564
586
|
}: {
|
|
565
587
|
onInstall: (server: RegistryServer) => void;
|
|
566
588
|
}) {
|
|
589
|
+
const { authFetch } = useAuth();
|
|
567
590
|
const [search, setSearch] = useState("");
|
|
568
591
|
const [servers, setServers] = useState<RegistryServer[]>([]);
|
|
569
592
|
const [loading, setLoading] = useState(false);
|
|
@@ -575,7 +598,7 @@ function RegistryBrowser({
|
|
|
575
598
|
setLoading(true);
|
|
576
599
|
setError(null);
|
|
577
600
|
try {
|
|
578
|
-
const res = await
|
|
601
|
+
const res = await authFetch(`/api/mcp/registry?search=${encodeURIComponent(query)}&limit=20`);
|
|
579
602
|
const data = await res.json();
|
|
580
603
|
if (!res.ok) {
|
|
581
604
|
setError(data.error || "Failed to search registry");
|
|
@@ -610,11 +633,11 @@ function RegistryBrowser({
|
|
|
610
633
|
return;
|
|
611
634
|
}
|
|
612
635
|
|
|
613
|
-
setInstalling(server.
|
|
636
|
+
setInstalling(server.id);
|
|
614
637
|
setError(null);
|
|
615
638
|
|
|
616
639
|
try {
|
|
617
|
-
const res = await
|
|
640
|
+
const res = await authFetch("/api/mcp/servers", {
|
|
618
641
|
method: "POST",
|
|
619
642
|
headers: { "Content-Type": "application/json" },
|
|
620
643
|
body: JSON.stringify({
|
|
@@ -676,7 +699,7 @@ function RegistryBrowser({
|
|
|
676
699
|
<div className="grid gap-4 md:grid-cols-2">
|
|
677
700
|
{servers.map((server) => (
|
|
678
701
|
<div
|
|
679
|
-
key={server.
|
|
702
|
+
key={server.id}
|
|
680
703
|
className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 hover:border-[#333] transition"
|
|
681
704
|
>
|
|
682
705
|
<div className="flex items-start justify-between gap-3">
|
|
@@ -685,37 +708,37 @@ function RegistryBrowser({
|
|
|
685
708
|
<p className="text-sm text-[#666] mt-1 line-clamp-2">
|
|
686
709
|
{server.description || "No description"}
|
|
687
710
|
</p>
|
|
688
|
-
<div className="flex items-center gap-
|
|
689
|
-
{server.
|
|
690
|
-
{
|
|
691
|
-
|
|
692
|
-
|
|
711
|
+
<div className="flex items-center gap-2 mt-2 text-xs text-[#555]">
|
|
712
|
+
{server.version && <span>v{server.version}</span>}
|
|
713
|
+
<span className={`px-1.5 py-0.5 rounded ${
|
|
714
|
+
server.npmPackage ? "bg-green-500/10 text-green-400" : "bg-blue-500/10 text-blue-400"
|
|
715
|
+
}`}>
|
|
716
|
+
{server.npmPackage ? "npm" : "remote"}
|
|
717
|
+
</span>
|
|
693
718
|
</div>
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
</code>
|
|
698
|
-
)}
|
|
719
|
+
<code className="text-xs text-[#555] bg-[#0a0a0a] px-2 py-0.5 rounded mt-2 inline-block truncate max-w-full">
|
|
720
|
+
{server.npmPackage || server.fullName}
|
|
721
|
+
</code>
|
|
699
722
|
</div>
|
|
700
723
|
<div className="flex-shrink-0">
|
|
701
724
|
{server.npmPackage ? (
|
|
702
725
|
<button
|
|
703
726
|
onClick={() => installServer(server)}
|
|
704
|
-
disabled={installing === server.
|
|
727
|
+
disabled={installing === server.id}
|
|
705
728
|
className="text-sm bg-[#1a1a1a] hover:bg-[#222] border border-[#333] hover:border-[#f97316] px-3 py-1.5 rounded transition disabled:opacity-50"
|
|
706
729
|
>
|
|
707
|
-
{installing === server.
|
|
730
|
+
{installing === server.id ? "Adding..." : "Add"}
|
|
708
731
|
</button>
|
|
709
|
-
) : (
|
|
732
|
+
) : server.repository ? (
|
|
710
733
|
<a
|
|
711
|
-
href={server.
|
|
734
|
+
href={server.repository}
|
|
712
735
|
target="_blank"
|
|
713
736
|
rel="noopener noreferrer"
|
|
714
737
|
className="text-sm text-[#666] hover:text-[#f97316] transition"
|
|
715
738
|
>
|
|
716
739
|
View →
|
|
717
740
|
</a>
|
|
718
|
-
)}
|
|
741
|
+
) : null}
|
|
719
742
|
</div>
|
|
720
743
|
</div>
|
|
721
744
|
</div>
|
|
@@ -749,6 +772,211 @@ function RegistryBrowser({
|
|
|
749
772
|
);
|
|
750
773
|
}
|
|
751
774
|
|
|
775
|
+
// Hosted MCP Services (Composio, Smithery, etc.)
|
|
776
|
+
interface ComposioConfig {
|
|
777
|
+
id: string;
|
|
778
|
+
name: string;
|
|
779
|
+
toolkits: string[];
|
|
780
|
+
toolsCount: number;
|
|
781
|
+
mcpUrl: string;
|
|
782
|
+
createdAt?: string;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function HostedServices() {
|
|
786
|
+
const { authFetch } = useAuth();
|
|
787
|
+
const [composioConnected, setComposioConnected] = useState(false);
|
|
788
|
+
const [smitheryConnected, setSmitheryConnected] = useState(false);
|
|
789
|
+
const [composioConfigs, setComposioConfigs] = useState<ComposioConfig[]>([]);
|
|
790
|
+
const [loading, setLoading] = useState(true);
|
|
791
|
+
const [loadingConfigs, setLoadingConfigs] = useState(false);
|
|
792
|
+
|
|
793
|
+
const fetchStatus = async () => {
|
|
794
|
+
try {
|
|
795
|
+
const res = await authFetch("/api/providers");
|
|
796
|
+
const data = await res.json();
|
|
797
|
+
const providers = data.providers || [];
|
|
798
|
+
const composio = providers.find((p: any) => p.id === "composio");
|
|
799
|
+
const smithery = providers.find((p: any) => p.id === "smithery");
|
|
800
|
+
setComposioConnected(composio?.hasKey || false);
|
|
801
|
+
setSmitheryConnected(smithery?.hasKey || false);
|
|
802
|
+
|
|
803
|
+
if (composio?.hasKey) {
|
|
804
|
+
fetchComposioConfigs();
|
|
805
|
+
}
|
|
806
|
+
} catch (e) {
|
|
807
|
+
console.error("Failed to fetch providers:", e);
|
|
808
|
+
}
|
|
809
|
+
setLoading(false);
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const fetchComposioConfigs = async () => {
|
|
813
|
+
setLoadingConfigs(true);
|
|
814
|
+
try {
|
|
815
|
+
const res = await authFetch("/api/integrations/composio/configs");
|
|
816
|
+
const data = await res.json();
|
|
817
|
+
setComposioConfigs(data.configs || []);
|
|
818
|
+
} catch (e) {
|
|
819
|
+
console.error("Failed to fetch Composio configs:", e);
|
|
820
|
+
}
|
|
821
|
+
setLoadingConfigs(false);
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
useEffect(() => {
|
|
825
|
+
fetchStatus();
|
|
826
|
+
}, [authFetch]);
|
|
827
|
+
|
|
828
|
+
if (loading) {
|
|
829
|
+
return <div className="text-center py-8 text-[#666]">Loading...</div>;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const hasAnyConnection = composioConnected || smitheryConnected;
|
|
833
|
+
|
|
834
|
+
if (!hasAnyConnection) {
|
|
835
|
+
return (
|
|
836
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-8 text-center">
|
|
837
|
+
<p className="text-[#888] mb-2">No hosted MCP services connected</p>
|
|
838
|
+
<p className="text-sm text-[#666] mb-4">
|
|
839
|
+
Connect Composio or Smithery in Settings to access cloud-based MCP servers.
|
|
840
|
+
</p>
|
|
841
|
+
<a
|
|
842
|
+
href="/settings"
|
|
843
|
+
className="inline-block bg-[#1a1a1a] hover:bg-[#222] border border-[#333] hover:border-[#f97316] px-4 py-2 rounded text-sm font-medium transition"
|
|
844
|
+
>
|
|
845
|
+
Go to Settings →
|
|
846
|
+
</a>
|
|
847
|
+
</div>
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return (
|
|
852
|
+
<div className="space-y-6">
|
|
853
|
+
{/* Composio MCP Configs */}
|
|
854
|
+
{composioConnected && (
|
|
855
|
+
<div>
|
|
856
|
+
<div className="flex items-center justify-between mb-3">
|
|
857
|
+
<div className="flex items-center gap-2">
|
|
858
|
+
<h2 className="font-medium">Composio</h2>
|
|
859
|
+
<span className="text-xs text-green-400">Connected</span>
|
|
860
|
+
</div>
|
|
861
|
+
<div className="flex items-center gap-3">
|
|
862
|
+
<button
|
|
863
|
+
onClick={fetchComposioConfigs}
|
|
864
|
+
disabled={loadingConfigs}
|
|
865
|
+
className="text-xs text-[#666] hover:text-[#888] transition"
|
|
866
|
+
>
|
|
867
|
+
{loadingConfigs ? "Loading..." : "Refresh"}
|
|
868
|
+
</button>
|
|
869
|
+
<a
|
|
870
|
+
href="https://app.composio.dev/mcp_configs"
|
|
871
|
+
target="_blank"
|
|
872
|
+
rel="noopener noreferrer"
|
|
873
|
+
className="text-xs text-[#666] hover:text-[#f97316] transition"
|
|
874
|
+
>
|
|
875
|
+
Create Config →
|
|
876
|
+
</a>
|
|
877
|
+
</div>
|
|
878
|
+
</div>
|
|
879
|
+
|
|
880
|
+
{loadingConfigs ? (
|
|
881
|
+
<div className="text-center py-6 text-[#666]">Loading configs...</div>
|
|
882
|
+
) : composioConfigs.length === 0 ? (
|
|
883
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
|
|
884
|
+
<p className="text-sm text-[#666]">No MCP configs found</p>
|
|
885
|
+
<a
|
|
886
|
+
href="https://app.composio.dev/mcp_configs"
|
|
887
|
+
target="_blank"
|
|
888
|
+
rel="noopener noreferrer"
|
|
889
|
+
className="text-xs text-[#f97316] hover:text-[#fb923c] mt-1 inline-block"
|
|
890
|
+
>
|
|
891
|
+
Create one in Composio →
|
|
892
|
+
</a>
|
|
893
|
+
</div>
|
|
894
|
+
) : (
|
|
895
|
+
<div className="space-y-2">
|
|
896
|
+
{composioConfigs.map((config) => (
|
|
897
|
+
<div
|
|
898
|
+
key={config.id}
|
|
899
|
+
className="bg-[#111] border border-[#1a1a1a] rounded-lg p-3 hover:border-[#333] transition flex items-center justify-between"
|
|
900
|
+
>
|
|
901
|
+
<div className="flex-1 min-w-0">
|
|
902
|
+
<div className="flex items-center gap-2">
|
|
903
|
+
<span className="font-medium text-sm">{config.name}</span>
|
|
904
|
+
<span className="text-xs text-[#555]">{config.toolsCount} tools</span>
|
|
905
|
+
</div>
|
|
906
|
+
{config.toolkits.length > 0 && (
|
|
907
|
+
<div className="flex flex-wrap gap-1 mt-1">
|
|
908
|
+
{config.toolkits.slice(0, 4).map((toolkit) => (
|
|
909
|
+
<span
|
|
910
|
+
key={toolkit}
|
|
911
|
+
className="text-xs bg-[#1a1a1a] text-[#666] px-1.5 py-0.5 rounded"
|
|
912
|
+
>
|
|
913
|
+
{toolkit}
|
|
914
|
+
</span>
|
|
915
|
+
))}
|
|
916
|
+
{config.toolkits.length > 4 && (
|
|
917
|
+
<span className="text-xs text-[#555]">+{config.toolkits.length - 4}</span>
|
|
918
|
+
)}
|
|
919
|
+
</div>
|
|
920
|
+
)}
|
|
921
|
+
</div>
|
|
922
|
+
<div className="flex items-center gap-2 ml-3">
|
|
923
|
+
<button
|
|
924
|
+
onClick={() => navigator.clipboard.writeText(`composio:${config.id}`)}
|
|
925
|
+
className="text-xs text-[#666] hover:text-[#f97316] px-2 py-1 transition"
|
|
926
|
+
title="Copy config ID"
|
|
927
|
+
>
|
|
928
|
+
Copy ID
|
|
929
|
+
</button>
|
|
930
|
+
<a
|
|
931
|
+
href={`https://app.composio.dev/mcp_configs/${config.id}`}
|
|
932
|
+
target="_blank"
|
|
933
|
+
rel="noopener noreferrer"
|
|
934
|
+
className="text-xs text-[#666] hover:text-[#888] transition"
|
|
935
|
+
>
|
|
936
|
+
Edit
|
|
937
|
+
</a>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
))}
|
|
941
|
+
</div>
|
|
942
|
+
)}
|
|
943
|
+
</div>
|
|
944
|
+
)}
|
|
945
|
+
|
|
946
|
+
{/* Smithery - placeholder for when we have API support */}
|
|
947
|
+
{smitheryConnected && (
|
|
948
|
+
<div>
|
|
949
|
+
<div className="flex items-center justify-between mb-3">
|
|
950
|
+
<div className="flex items-center gap-2">
|
|
951
|
+
<h2 className="font-medium">Smithery</h2>
|
|
952
|
+
<span className="text-xs text-green-400">Connected</span>
|
|
953
|
+
</div>
|
|
954
|
+
<a
|
|
955
|
+
href="https://smithery.ai/servers"
|
|
956
|
+
target="_blank"
|
|
957
|
+
rel="noopener noreferrer"
|
|
958
|
+
className="text-xs text-[#666] hover:text-[#f97316] transition"
|
|
959
|
+
>
|
|
960
|
+
View Servers →
|
|
961
|
+
</a>
|
|
962
|
+
</div>
|
|
963
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
|
|
964
|
+
<p className="text-sm text-[#666]">
|
|
965
|
+
Smithery servers can be added from the Registry tab.
|
|
966
|
+
</p>
|
|
967
|
+
</div>
|
|
968
|
+
</div>
|
|
969
|
+
)}
|
|
970
|
+
|
|
971
|
+
<div className="p-3 bg-[#0a0a0a] border border-[#222] rounded text-xs text-[#666]">
|
|
972
|
+
<strong className="text-[#888]">Tip:</strong> Copy a config ID (e.g., <code className="bg-[#111] px-1 rounded">composio:abc123</code>) and add it to your agent's MCP servers.
|
|
973
|
+
{" · "}
|
|
974
|
+
<a href="/settings" className="text-[#f97316] hover:text-[#fb923c]">Add more providers in Settings</a>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
752
980
|
// Parse command and extract credential placeholders
|
|
753
981
|
function parseCommandForCredentials(cmd: string): {
|
|
754
982
|
cleanCommand: string;
|
|
@@ -810,6 +1038,7 @@ function AddServerModal({
|
|
|
810
1038
|
onClose: () => void;
|
|
811
1039
|
onAdded: () => void;
|
|
812
1040
|
}) {
|
|
1041
|
+
const { authFetch } = useAuth();
|
|
813
1042
|
const [mode, setMode] = useState<"npm" | "command">("npm");
|
|
814
1043
|
const [name, setName] = useState("");
|
|
815
1044
|
const [pkg, setPkg] = useState("");
|
|
@@ -941,7 +1170,7 @@ function AddServerModal({
|
|
|
941
1170
|
body.env = env;
|
|
942
1171
|
}
|
|
943
1172
|
|
|
944
|
-
const res = await
|
|
1173
|
+
const res = await authFetch("/api/mcp/servers", {
|
|
945
1174
|
method: "POST",
|
|
946
1175
|
headers: { "Content-Type": "application/json" },
|
|
947
1176
|
body: JSON.stringify(body),
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { CheckIcon } from "../common/Icons";
|
|
3
|
+
import { CreateAccountStep } from "../auth";
|
|
3
4
|
import type { Provider } from "../../types";
|
|
4
5
|
|
|
5
6
|
interface OnboardingWizardProps {
|
|
6
7
|
onComplete: () => void;
|
|
8
|
+
needsAccount?: boolean; // Whether to show account creation step
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
10
|
-
|
|
11
|
+
export function OnboardingWizard({ onComplete, needsAccount = false }: OnboardingWizardProps) {
|
|
12
|
+
// Step 0 = account creation (if needed), Step 1 = add keys, Step 2 = complete
|
|
13
|
+
const [step, setStep] = useState(needsAccount ? 0 : 1);
|
|
11
14
|
const [providers, setProviders] = useState<Provider[]>([]);
|
|
12
15
|
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
|
13
16
|
const [apiKey, setApiKey] = useState("");
|
|
@@ -15,16 +18,26 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
|
15
18
|
const [testing, setTesting] = useState(false);
|
|
16
19
|
const [error, setError] = useState<string | null>(null);
|
|
17
20
|
const [success, setSuccess] = useState<string | null>(null);
|
|
21
|
+
const [accountCreated, setAccountCreated] = useState(false);
|
|
22
|
+
|
|
23
|
+
// Get auth token from session storage (set during account creation)
|
|
24
|
+
const getAuthHeaders = (): Record<string, string> => {
|
|
25
|
+
const token = sessionStorage.getItem("accessToken");
|
|
26
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
27
|
+
};
|
|
18
28
|
|
|
19
29
|
useEffect(() => {
|
|
20
|
-
fetch(
|
|
30
|
+
// Don't fetch providers until after account is created (if needed)
|
|
31
|
+
if (needsAccount && !accountCreated && step === 0) return;
|
|
32
|
+
|
|
33
|
+
fetch("/api/providers", { headers: getAuthHeaders() })
|
|
21
34
|
.then(res => res.json())
|
|
22
35
|
.then(data => {
|
|
23
36
|
// Only show LLM providers in onboarding, not integrations
|
|
24
37
|
const llmProviders = (data.providers || []).filter((p: Provider) => p.type === "llm");
|
|
25
38
|
setProviders(llmProviders);
|
|
26
39
|
});
|
|
27
|
-
}, []);
|
|
40
|
+
}, [accountCreated, step, needsAccount]);
|
|
28
41
|
|
|
29
42
|
const configuredProviders = providers.filter(p => p.hasKey);
|
|
30
43
|
|
|
@@ -38,7 +51,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
|
38
51
|
setTesting(true);
|
|
39
52
|
const testRes = await fetch(`/api/keys/${selectedProvider}/test`, {
|
|
40
53
|
method: "POST",
|
|
41
|
-
headers: { "Content-Type": "application/json" },
|
|
54
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
42
55
|
body: JSON.stringify({ key: apiKey }),
|
|
43
56
|
});
|
|
44
57
|
const testData = await testRes.json();
|
|
@@ -52,7 +65,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
|
52
65
|
|
|
53
66
|
const saveRes = await fetch(`/api/keys/${selectedProvider}`, {
|
|
54
67
|
method: "POST",
|
|
55
|
-
headers: { "Content-Type": "application/json" },
|
|
68
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
56
69
|
body: JSON.stringify({ key: apiKey }),
|
|
57
70
|
});
|
|
58
71
|
const saveData = await saveRes.json();
|
|
@@ -62,7 +75,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
|
62
75
|
} else {
|
|
63
76
|
setSuccess("API key saved successfully!");
|
|
64
77
|
setApiKey("");
|
|
65
|
-
const res = await fetch("/api/providers");
|
|
78
|
+
const res = await fetch("/api/providers", { headers: getAuthHeaders() });
|
|
66
79
|
const data = await res.json();
|
|
67
80
|
setProviders(data.providers || []);
|
|
68
81
|
setSelectedProvider(null);
|
|
@@ -74,10 +87,43 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
|
74
87
|
};
|
|
75
88
|
|
|
76
89
|
const completeOnboarding = async () => {
|
|
77
|
-
|
|
90
|
+
// Create a default project for the user
|
|
91
|
+
try {
|
|
92
|
+
const projectRes = await fetch("/api/projects", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
name: "My Project",
|
|
97
|
+
description: "Default project for organizing agents",
|
|
98
|
+
color: "#f97316", // Orange - matches brand color
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (projectRes.ok) {
|
|
103
|
+
const data = await projectRes.json();
|
|
104
|
+
// Set this project as the current project in localStorage
|
|
105
|
+
if (data.project?.id) {
|
|
106
|
+
localStorage.setItem("apteva_current_project", data.project.id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// Don't block onboarding if project creation fails
|
|
111
|
+
console.error("Failed to create default project:", e);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await fetch("/api/onboarding/complete", { method: "POST", headers: getAuthHeaders() });
|
|
78
115
|
onComplete();
|
|
79
116
|
};
|
|
80
117
|
|
|
118
|
+
const handleAccountCreated = () => {
|
|
119
|
+
setAccountCreated(true);
|
|
120
|
+
setStep(1);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Calculate total steps and current progress
|
|
124
|
+
const totalSteps = needsAccount ? 3 : 2;
|
|
125
|
+
const currentStep = needsAccount ? step : step - 1;
|
|
126
|
+
|
|
81
127
|
return (
|
|
82
128
|
<div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex items-center justify-center p-8">
|
|
83
129
|
<div className="w-full max-w-2xl">
|
|
@@ -92,12 +138,22 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
|
92
138
|
|
|
93
139
|
{/* Progress */}
|
|
94
140
|
<div className="flex items-center justify-center gap-2 mb-8">
|
|
141
|
+
{needsAccount && (
|
|
142
|
+
<>
|
|
143
|
+
<div className={`w-3 h-3 rounded-full ${step >= 0 ? 'bg-[#f97316]' : 'bg-[#333]'}`} />
|
|
144
|
+
<div className={`w-16 h-0.5 ${step >= 1 ? 'bg-[#f97316]' : 'bg-[#333]'}`} />
|
|
145
|
+
</>
|
|
146
|
+
)}
|
|
95
147
|
<div className={`w-3 h-3 rounded-full ${step >= 1 ? 'bg-[#f97316]' : 'bg-[#333]'}`} />
|
|
96
148
|
<div className={`w-16 h-0.5 ${step >= 2 ? 'bg-[#f97316]' : 'bg-[#333]'}`} />
|
|
97
149
|
<div className={`w-3 h-3 rounded-full ${step >= 2 ? 'bg-[#f97316]' : 'bg-[#333]'}`} />
|
|
98
150
|
</div>
|
|
99
151
|
|
|
100
152
|
<div className="bg-[#111] rounded-lg border border-[#1a1a1a] p-8">
|
|
153
|
+
{step === 0 && needsAccount && (
|
|
154
|
+
<CreateAccountStep onComplete={handleAccountCreated} />
|
|
155
|
+
)}
|
|
156
|
+
|
|
101
157
|
{step === 1 && (
|
|
102
158
|
<Step1AddKeys
|
|
103
159
|
providers={providers}
|