@cortexmemory/cli 0.27.1 → 0.27.4

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 (51) hide show
  1. package/dist/commands/convex.js +1 -1
  2. package/dist/commands/convex.js.map +1 -1
  3. package/dist/commands/deploy.d.ts +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +839 -141
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/dev.d.ts.map +1 -1
  8. package/dist/commands/dev.js +89 -26
  9. package/dist/commands/dev.js.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/utils/app-template-sync.d.ts +95 -0
  12. package/dist/utils/app-template-sync.d.ts.map +1 -0
  13. package/dist/utils/app-template-sync.js +445 -0
  14. package/dist/utils/app-template-sync.js.map +1 -0
  15. package/dist/utils/deployment-selector.d.ts +21 -0
  16. package/dist/utils/deployment-selector.d.ts.map +1 -1
  17. package/dist/utils/deployment-selector.js +32 -0
  18. package/dist/utils/deployment-selector.js.map +1 -1
  19. package/dist/utils/init/graph-setup.d.ts.map +1 -1
  20. package/dist/utils/init/graph-setup.js +13 -2
  21. package/dist/utils/init/graph-setup.js.map +1 -1
  22. package/package.json +1 -1
  23. package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +30 -0
  24. package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +128 -0
  25. package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +94 -0
  26. package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +59 -0
  27. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +139 -3
  28. package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +333 -0
  29. package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +179 -0
  30. package/templates/vercel-ai-quickstart/app/globals.css +161 -0
  31. package/templates/vercel-ai-quickstart/app/page.tsx +110 -11
  32. package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +139 -0
  33. package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +283 -0
  34. package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +323 -0
  35. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +117 -17
  36. package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +202 -0
  37. package/templates/vercel-ai-quickstart/jest.config.js +52 -0
  38. package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +165 -0
  39. package/templates/vercel-ai-quickstart/lib/cortex.ts +27 -0
  40. package/templates/vercel-ai-quickstart/lib/password.ts +120 -0
  41. package/templates/vercel-ai-quickstart/lib/versions.ts +60 -0
  42. package/templates/vercel-ai-quickstart/next.config.js +20 -0
  43. package/templates/vercel-ai-quickstart/package.json +11 -2
  44. package/templates/vercel-ai-quickstart/test-api.mjs +272 -0
  45. package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +454 -0
  46. package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +263 -0
  47. package/templates/vercel-ai-quickstart/tests/helpers/setup.ts +48 -0
  48. package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +455 -0
  49. package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +461 -0
  50. package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +228 -0
  51. package/templates/vercel-ai-quickstart/tsconfig.json +1 -1
@@ -1,8 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  import dynamic from "next/dynamic";
4
- import { useState } from "react";
4
+ import { useState, useCallback, useEffect } from "react";
5
5
  import { useLayerTracking } from "@/lib/layer-tracking";
6
+ import { detectVersions, type VersionInfo } from "@/lib/versions";
7
+ import { AuthProvider, useAuth } from "@/components/AuthProvider";
8
+ import { AdminSetup } from "@/components/AdminSetup";
9
+ import { LoginScreen } from "@/components/LoginScreen";
6
10
 
7
11
  // Dynamic imports to avoid SSR issues with framer-motion
8
12
  const ChatInterface = dynamic(
@@ -33,10 +37,23 @@ const HealthStatus = dynamic(
33
37
  })),
34
38
  { ssr: false },
35
39
  );
40
+ const ChatHistorySidebar = dynamic(
41
+ () =>
42
+ import("@/components/ChatHistorySidebar").then((m) => ({
43
+ default: m.ChatHistorySidebar,
44
+ })),
45
+ { ssr: false },
46
+ );
36
47
 
37
- export default function Home() {
48
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
49
+ // Main App Content (with auth)
50
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
51
+
52
+ function MainContent() {
53
+ const { isLoading, isAdminSetup, isAuthenticated, user } = useAuth();
38
54
  const [memorySpaceId, setMemorySpaceId] = useState("quickstart-demo");
39
- const [userId] = useState("demo-user");
55
+ const [currentConversationId, setCurrentConversationId] = useState<string | null>(null);
56
+ const [versions, setVersions] = useState<VersionInfo | null>(null);
40
57
  const {
41
58
  layers,
42
59
  isOrchestrating,
@@ -45,11 +62,64 @@ export default function Home() {
45
62
  resetLayers,
46
63
  } = useLayerTracking();
47
64
 
65
+ // Detect SDK versions on mount
66
+ useEffect(() => {
67
+ detectVersions().then(setVersions);
68
+ }, []);
69
+
70
+ // Handle new chat
71
+ const handleNewChat = useCallback(() => {
72
+ setCurrentConversationId(null);
73
+ resetLayers();
74
+ }, [resetLayers]);
75
+
76
+ // Handle conversation selection
77
+ const handleSelectConversation = useCallback((conversationId: string) => {
78
+ setCurrentConversationId(conversationId);
79
+ resetLayers();
80
+ }, [resetLayers]);
81
+
82
+ // Handle conversation update (e.g., title change after first message)
83
+ const handleConversationUpdate = useCallback((conversationId: string) => {
84
+ // Update current conversation ID if it was null (new chat created)
85
+ if (!currentConversationId) {
86
+ setCurrentConversationId(conversationId);
87
+ }
88
+ }, [currentConversationId]);
89
+
90
+ // Loading state
91
+ if (isLoading) {
92
+ return (
93
+ <div className="min-h-screen flex items-center justify-center">
94
+ <div className="text-center">
95
+ <div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center animate-pulse">
96
+ <span className="text-3xl">🧠</span>
97
+ </div>
98
+ <p className="text-gray-400">Loading Cortex...</p>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
103
+
104
+ // First-run: Admin setup
105
+ if (isAdminSetup === false) {
106
+ return <AdminSetup />;
107
+ }
108
+
109
+ // Not authenticated: Login/Register
110
+ if (!isAuthenticated) {
111
+ return <LoginScreen />;
112
+ }
113
+
114
+ // Get userId from authenticated user
115
+ const userId = user?.id || "demo-user";
116
+
117
+ // Main authenticated interface
48
118
  return (
49
- <main className="min-h-screen flex flex-col">
119
+ <main className="h-screen flex flex-col overflow-hidden">
50
120
  {/* Header */}
51
121
  <header className="border-b border-white/10 px-6 py-4">
52
- <div className="max-w-7xl mx-auto flex items-center justify-between">
122
+ <div className="flex items-center justify-between">
53
123
  <div className="flex items-center gap-3">
54
124
  <div className="w-10 h-10 rounded-lg bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center">
55
125
  <span className="text-xl">🧠</span>
@@ -72,20 +142,31 @@ export default function Home() {
72
142
  </div>
73
143
  </header>
74
144
 
75
- {/* Main Content */}
145
+ {/* Main Content - Three Column Layout */}
76
146
  <div className="flex-1 flex overflow-hidden">
77
- {/* Chat Section */}
147
+ {/* Left: Chat History Sidebar */}
148
+ <ChatHistorySidebar
149
+ memorySpaceId={memorySpaceId}
150
+ currentConversationId={currentConversationId}
151
+ onSelectConversation={handleSelectConversation}
152
+ onNewChat={handleNewChat}
153
+ />
154
+
155
+ {/* Center: Chat Section */}
78
156
  <div className="flex-1 flex flex-col border-r border-white/10">
79
157
  <ChatInterface
80
158
  memorySpaceId={memorySpaceId}
81
159
  userId={userId}
160
+ conversationId={currentConversationId}
161
+ apiEndpoint={versions?.aiSdkMajor === 6 ? "/api/chat-v6" : "/api/chat"}
82
162
  onOrchestrationStart={startOrchestration}
83
163
  onLayerUpdate={updateLayer}
84
164
  onReset={resetLayers}
165
+ onConversationUpdate={handleConversationUpdate}
85
166
  />
86
167
  </div>
87
168
 
88
- {/* Layer Flow Visualization */}
169
+ {/* Right: Layer Flow Visualization */}
89
170
  <div className="w-[480px] flex flex-col bg-black/20">
90
171
  <div className="p-4 border-b border-white/10">
91
172
  <h2 className="font-semibold flex items-center gap-2">
@@ -110,11 +191,17 @@ export default function Home() {
110
191
 
111
192
  {/* Footer */}
112
193
  <footer className="border-t border-white/10 px-6 py-3">
113
- <div className="max-w-7xl mx-auto flex items-center justify-between text-sm text-gray-500">
194
+ <div className="flex items-center justify-between text-sm text-gray-500">
114
195
  <div className="flex items-center gap-4">
115
- <span>Cortex SDK v0.24.0</span>
196
+ <span>Cortex SDK {versions ? `v${versions.cortexSdk}` : "..."}</span>
116
197
  <span>•</span>
117
- <span>Vercel AI SDK v5</span>
198
+ <span>Vercel AI SDK {versions?.aiSdk ?? "..."}</span>
199
+ {versions?.aiSdkMajor === 6 && (
200
+ <>
201
+ <span>•</span>
202
+ <span className="text-cortex-400">Using ToolLoopAgent</span>
203
+ </>
204
+ )}
118
205
  </div>
119
206
  <a
120
207
  href="https://cortexmemory.dev/docs"
@@ -129,3 +216,15 @@ export default function Home() {
129
216
  </main>
130
217
  );
131
218
  }
219
+
220
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
221
+ // Page Component (wraps with AuthProvider)
222
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
223
+
224
+ export default function Home() {
225
+ return (
226
+ <AuthProvider>
227
+ <MainContent />
228
+ </AuthProvider>
229
+ );
230
+ }
@@ -0,0 +1,139 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useAuth } from "./AuthProvider";
5
+
6
+ export function AdminSetup() {
7
+ const { setupAdmin, error, clearError } = useAuth();
8
+ const [password, setPassword] = useState("");
9
+ const [confirmPassword, setConfirmPassword] = useState("");
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [localError, setLocalError] = useState<string | null>(null);
12
+
13
+ const handleSubmit = async (e: React.FormEvent) => {
14
+ e.preventDefault();
15
+ setLocalError(null);
16
+ clearError();
17
+
18
+ if (password.length < 4) {
19
+ setLocalError("Password must be at least 4 characters");
20
+ return;
21
+ }
22
+
23
+ if (password !== confirmPassword) {
24
+ setLocalError("Passwords do not match");
25
+ return;
26
+ }
27
+
28
+ setIsLoading(true);
29
+ await setupAdmin(password);
30
+ setIsLoading(false);
31
+ };
32
+
33
+ const displayError = localError || error;
34
+
35
+ return (
36
+ <div className="min-h-screen flex items-center justify-center p-4">
37
+ <div className="w-full max-w-md">
38
+ {/* Logo and Title */}
39
+ <div className="text-center mb-8">
40
+ <div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center shadow-lg shadow-cortex-500/20">
41
+ <span className="text-4xl">🧠</span>
42
+ </div>
43
+ <h1 className="text-3xl font-bold mb-2">Welcome to Cortex</h1>
44
+ <p className="text-gray-400">Set up your admin password to get started</p>
45
+ </div>
46
+
47
+ {/* Setup Form */}
48
+ <div className="glass rounded-2xl p-8">
49
+ <form onSubmit={handleSubmit} className="space-y-6">
50
+ <div>
51
+ <label
52
+ htmlFor="password"
53
+ className="block text-sm font-medium text-gray-300 mb-2"
54
+ >
55
+ Admin Password
56
+ </label>
57
+ <input
58
+ id="password"
59
+ type="password"
60
+ value={password}
61
+ onChange={(e) => setPassword(e.target.value)}
62
+ placeholder="Enter a secure password"
63
+ className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:border-cortex-500 transition-colors"
64
+ disabled={isLoading}
65
+ autoFocus
66
+ />
67
+ </div>
68
+
69
+ <div>
70
+ <label
71
+ htmlFor="confirmPassword"
72
+ className="block text-sm font-medium text-gray-300 mb-2"
73
+ >
74
+ Confirm Password
75
+ </label>
76
+ <input
77
+ id="confirmPassword"
78
+ type="password"
79
+ value={confirmPassword}
80
+ onChange={(e) => setConfirmPassword(e.target.value)}
81
+ placeholder="Confirm your password"
82
+ className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:border-cortex-500 transition-colors"
83
+ disabled={isLoading}
84
+ />
85
+ </div>
86
+
87
+ {displayError && (
88
+ <div className="p-3 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400 text-sm">
89
+ {displayError}
90
+ </div>
91
+ )}
92
+
93
+ <button
94
+ type="submit"
95
+ disabled={isLoading || !password || !confirmPassword}
96
+ className="w-full py-3 bg-cortex-600 hover:bg-cortex-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-xl font-medium transition-colors flex items-center justify-center gap-2"
97
+ >
98
+ {isLoading ? (
99
+ <>
100
+ <svg
101
+ className="animate-spin h-5 w-5"
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ fill="none"
104
+ viewBox="0 0 24 24"
105
+ >
106
+ <circle
107
+ className="opacity-25"
108
+ cx="12"
109
+ cy="12"
110
+ r="10"
111
+ stroke="currentColor"
112
+ strokeWidth="4"
113
+ />
114
+ <path
115
+ className="opacity-75"
116
+ fill="currentColor"
117
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
118
+ />
119
+ </svg>
120
+ Setting up...
121
+ </>
122
+ ) : (
123
+ "Set Admin Password"
124
+ )}
125
+ </button>
126
+ </form>
127
+
128
+ <div className="mt-6 pt-6 border-t border-white/10">
129
+ <p className="text-xs text-gray-500 text-center">
130
+ This password will be used to manage the quickstart demo.
131
+ <br />
132
+ You can create additional user accounts after setup.
133
+ </p>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ );
139
+ }
@@ -0,0 +1,283 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ useEffect,
8
+ useCallback,
9
+ type ReactNode,
10
+ } from "react";
11
+
12
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ // Types
14
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
15
+
16
+ export interface User {
17
+ id: string;
18
+ displayName: string;
19
+ }
20
+
21
+ export interface AuthState {
22
+ isLoading: boolean;
23
+ isAdminSetup: boolean | null;
24
+ isAuthenticated: boolean;
25
+ user: User | null;
26
+ error: string | null;
27
+ }
28
+
29
+ export interface AuthContextValue extends AuthState {
30
+ setupAdmin: (password: string) => Promise<boolean>;
31
+ login: (username: string, password: string) => Promise<boolean>;
32
+ register: (
33
+ username: string,
34
+ password: string,
35
+ displayName?: string
36
+ ) => Promise<boolean>;
37
+ logout: () => void;
38
+ clearError: () => void;
39
+ }
40
+
41
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
42
+ // Context
43
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
44
+
45
+ const AuthContext = createContext<AuthContextValue | null>(null);
46
+
47
+ const STORAGE_KEY = "cortex-quickstart-auth";
48
+
49
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
50
+ // Provider
51
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
+
53
+ export function AuthProvider({ children }: { children: ReactNode }) {
54
+ const [state, setState] = useState<AuthState>({
55
+ isLoading: true,
56
+ isAdminSetup: null,
57
+ isAuthenticated: false,
58
+ user: null,
59
+ error: null,
60
+ });
61
+
62
+ // Check admin setup status and restore session on mount
63
+ useEffect(() => {
64
+ const init = async () => {
65
+ try {
66
+ // Check if admin is set up
67
+ const checkResponse = await fetch("/api/auth/check");
68
+ const checkData = await checkResponse.json();
69
+
70
+ // Try to restore session from localStorage
71
+ let user: User | null = null;
72
+ let sessionToken: string | null = null;
73
+
74
+ if (typeof window !== "undefined") {
75
+ const stored = localStorage.getItem(STORAGE_KEY);
76
+ if (stored) {
77
+ try {
78
+ const parsed = JSON.parse(stored);
79
+ user = parsed.user;
80
+ sessionToken = parsed.sessionToken;
81
+ } catch {
82
+ localStorage.removeItem(STORAGE_KEY);
83
+ }
84
+ }
85
+ }
86
+
87
+ setState({
88
+ isLoading: false,
89
+ isAdminSetup: checkData.isSetup,
90
+ isAuthenticated: !!user && !!sessionToken,
91
+ user,
92
+ error: null,
93
+ });
94
+ } catch (error) {
95
+ console.error("Auth init error:", error);
96
+ setState((prev) => ({
97
+ ...prev,
98
+ isLoading: false,
99
+ error: "Failed to initialize authentication",
100
+ }));
101
+ }
102
+ };
103
+
104
+ init();
105
+ }, []);
106
+
107
+ // Setup admin password
108
+ const setupAdmin = useCallback(async (password: string): Promise<boolean> => {
109
+ setState((prev) => ({ ...prev, error: null }));
110
+
111
+ try {
112
+ const response = await fetch("/api/auth/setup", {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify({ password }),
116
+ });
117
+
118
+ const data = await response.json();
119
+
120
+ if (!response.ok) {
121
+ setState((prev) => ({ ...prev, error: data.error }));
122
+ return false;
123
+ }
124
+
125
+ setState((prev) => ({
126
+ ...prev,
127
+ isAdminSetup: true,
128
+ }));
129
+
130
+ return true;
131
+ } catch (error) {
132
+ console.error("Setup error:", error);
133
+ setState((prev) => ({
134
+ ...prev,
135
+ error: "Failed to setup admin password",
136
+ }));
137
+ return false;
138
+ }
139
+ }, []);
140
+
141
+ // Login
142
+ const login = useCallback(
143
+ async (username: string, password: string): Promise<boolean> => {
144
+ setState((prev) => ({ ...prev, error: null }));
145
+
146
+ try {
147
+ const response = await fetch("/api/auth/login", {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json" },
150
+ body: JSON.stringify({ username, password }),
151
+ });
152
+
153
+ const data = await response.json();
154
+
155
+ if (!response.ok) {
156
+ setState((prev) => ({ ...prev, error: data.error }));
157
+ return false;
158
+ }
159
+
160
+ // Store session
161
+ if (typeof window !== "undefined") {
162
+ localStorage.setItem(
163
+ STORAGE_KEY,
164
+ JSON.stringify({
165
+ user: data.user,
166
+ sessionToken: data.sessionToken,
167
+ })
168
+ );
169
+ }
170
+
171
+ setState((prev) => ({
172
+ ...prev,
173
+ isAuthenticated: true,
174
+ user: data.user,
175
+ }));
176
+
177
+ return true;
178
+ } catch (error) {
179
+ console.error("Login error:", error);
180
+ setState((prev) => ({
181
+ ...prev,
182
+ error: "Failed to login",
183
+ }));
184
+ return false;
185
+ }
186
+ },
187
+ []
188
+ );
189
+
190
+ // Register
191
+ const register = useCallback(
192
+ async (
193
+ username: string,
194
+ password: string,
195
+ displayName?: string
196
+ ): Promise<boolean> => {
197
+ setState((prev) => ({ ...prev, error: null }));
198
+
199
+ try {
200
+ const response = await fetch("/api/auth/register", {
201
+ method: "POST",
202
+ headers: { "Content-Type": "application/json" },
203
+ body: JSON.stringify({ username, password, displayName }),
204
+ });
205
+
206
+ const data = await response.json();
207
+
208
+ if (!response.ok) {
209
+ setState((prev) => ({ ...prev, error: data.error }));
210
+ return false;
211
+ }
212
+
213
+ // Store session
214
+ if (typeof window !== "undefined") {
215
+ localStorage.setItem(
216
+ STORAGE_KEY,
217
+ JSON.stringify({
218
+ user: data.user,
219
+ sessionToken: data.sessionToken,
220
+ })
221
+ );
222
+ }
223
+
224
+ setState((prev) => ({
225
+ ...prev,
226
+ isAuthenticated: true,
227
+ user: data.user,
228
+ }));
229
+
230
+ return true;
231
+ } catch (error) {
232
+ console.error("Register error:", error);
233
+ setState((prev) => ({
234
+ ...prev,
235
+ error: "Failed to register",
236
+ }));
237
+ return false;
238
+ }
239
+ },
240
+ []
241
+ );
242
+
243
+ // Logout
244
+ const logout = useCallback(() => {
245
+ if (typeof window !== "undefined") {
246
+ localStorage.removeItem(STORAGE_KEY);
247
+ }
248
+
249
+ setState((prev) => ({
250
+ ...prev,
251
+ isAuthenticated: false,
252
+ user: null,
253
+ }));
254
+ }, []);
255
+
256
+ // Clear error
257
+ const clearError = useCallback(() => {
258
+ setState((prev) => ({ ...prev, error: null }));
259
+ }, []);
260
+
261
+ const value: AuthContextValue = {
262
+ ...state,
263
+ setupAdmin,
264
+ login,
265
+ register,
266
+ logout,
267
+ clearError,
268
+ };
269
+
270
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
271
+ }
272
+
273
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
274
+ // Hook
275
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
276
+
277
+ export function useAuth(): AuthContextValue {
278
+ const context = useContext(AuthContext);
279
+ if (!context) {
280
+ throw new Error("useAuth must be used within an AuthProvider");
281
+ }
282
+ return context;
283
+ }