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.
Files changed (41) hide show
  1. package/dist/App.hzbfeg94.js +217 -0
  2. package/dist/index.html +3 -1
  3. package/dist/styles.css +1 -1
  4. package/package.json +1 -1
  5. package/src/auth/index.ts +386 -0
  6. package/src/auth/middleware.ts +183 -0
  7. package/src/binary.ts +19 -1
  8. package/src/db.ts +570 -32
  9. package/src/routes/api.ts +913 -38
  10. package/src/routes/auth.ts +242 -0
  11. package/src/server.ts +60 -8
  12. package/src/web/App.tsx +61 -19
  13. package/src/web/components/agents/AgentCard.tsx +30 -41
  14. package/src/web/components/agents/AgentPanel.tsx +751 -11
  15. package/src/web/components/agents/AgentsView.tsx +81 -9
  16. package/src/web/components/agents/CreateAgentModal.tsx +28 -1
  17. package/src/web/components/auth/CreateAccountStep.tsx +176 -0
  18. package/src/web/components/auth/LoginPage.tsx +91 -0
  19. package/src/web/components/auth/index.ts +2 -0
  20. package/src/web/components/common/Icons.tsx +48 -0
  21. package/src/web/components/common/Modal.tsx +1 -1
  22. package/src/web/components/dashboard/Dashboard.tsx +91 -31
  23. package/src/web/components/index.ts +3 -0
  24. package/src/web/components/layout/Header.tsx +145 -15
  25. package/src/web/components/layout/Sidebar.tsx +81 -43
  26. package/src/web/components/mcp/McpPage.tsx +261 -32
  27. package/src/web/components/onboarding/OnboardingWizard.tsx +64 -8
  28. package/src/web/components/settings/SettingsPage.tsx +404 -18
  29. package/src/web/components/tasks/TasksPage.tsx +21 -19
  30. package/src/web/components/telemetry/TelemetryPage.tsx +271 -81
  31. package/src/web/context/AuthContext.tsx +230 -0
  32. package/src/web/context/ProjectContext.tsx +182 -0
  33. package/src/web/context/TelemetryContext.tsx +98 -76
  34. package/src/web/context/index.ts +5 -0
  35. package/src/web/hooks/useAgents.ts +18 -6
  36. package/src/web/hooks/useOnboarding.ts +20 -4
  37. package/src/web/hooks/useProviders.ts +15 -5
  38. package/src/web/icon.png +0 -0
  39. package/src/web/styles.css +12 -0
  40. package/src/web/types.ts +6 -0
  41. 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
- vendor: string;
22
- sourceUrl: string;
24
+ version?: string;
25
+ repository?: string;
23
26
  npmPackage: string | null;
24
- githubStars: number | null;
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 fetch("/api/mcp/servers");
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 fetch(`/api/mcp/servers/${id}/start`, { method: "POST" });
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 fetch(`/api/mcp/servers/${id}/stop`, { method: "POST" });
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 fetch(`/api/mcp/servers/${id}`, { method: "DELETE" });
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 fetch(`/api/mcp/servers/${server.id}/tools`);
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 fetch(`/api/mcp/servers/${serverId}/tools/${encodeURIComponent(tool.name)}/call`, {
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 fetch(`/api/mcp/registry?search=${encodeURIComponent(query)}&limit=20`);
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.name);
636
+ setInstalling(server.id);
614
637
  setError(null);
615
638
 
616
639
  try {
617
- const res = await fetch("/api/mcp/servers", {
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.name}
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-3 mt-2 text-xs text-[#555]">
689
- {server.vendor && <span>by {server.vendor}</span>}
690
- {server.githubStars !== null && server.githubStars > 0 && (
691
- <span>★ {server.githubStars.toLocaleString()}</span>
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
- {server.npmPackage && (
695
- <code className="text-xs text-[#666] bg-[#0a0a0a] px-2 py-0.5 rounded mt-2 inline-block truncate max-w-full">
696
- {server.npmPackage}
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.name}
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.name ? "Adding..." : "Add"}
730
+ {installing === server.id ? "Adding..." : "Add"}
708
731
  </button>
709
- ) : (
732
+ ) : server.repository ? (
710
733
  <a
711
- href={server.sourceUrl}
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 fetch("/api/mcp/servers", {
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
- const [step, setStep] = useState(1);
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("/api/providers")
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
- await fetch("/api/onboarding/complete", { method: "POST" });
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}