@cortexmemory/cli 0.27.4 → 0.28.0

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 (66) hide show
  1. package/dist/commands/db.d.ts.map +1 -1
  2. package/dist/commands/db.js +18 -6
  3. package/dist/commands/db.js.map +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +74 -34
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/dev.js +3 -2
  8. package/dist/commands/dev.js.map +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +12 -0
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/types.d.ts +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/app-template-sync.d.ts.map +1 -1
  15. package/dist/utils/app-template-sync.js +3 -1
  16. package/dist/utils/app-template-sync.js.map +1 -1
  17. package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
  18. package/dist/utils/init/quickstart-setup.js.map +1 -1
  19. package/package.json +4 -4
  20. package/templates/basic/.env.local.example +23 -0
  21. package/templates/basic/README.md +181 -56
  22. package/templates/basic/package-lock.json +2180 -406
  23. package/templates/basic/package.json +23 -5
  24. package/templates/basic/src/__tests__/chat.test.ts +340 -0
  25. package/templates/basic/src/__tests__/cortex.test.ts +260 -0
  26. package/templates/basic/src/__tests__/display.test.ts +455 -0
  27. package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
  28. package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
  29. package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
  30. package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
  31. package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
  32. package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
  33. package/templates/basic/src/__tests__/llm.test.ts +344 -0
  34. package/templates/basic/src/chat.ts +300 -0
  35. package/templates/basic/src/cortex.ts +203 -0
  36. package/templates/basic/src/display.ts +425 -0
  37. package/templates/basic/src/index.ts +194 -64
  38. package/templates/basic/src/llm.ts +214 -0
  39. package/templates/basic/src/server.ts +280 -0
  40. package/templates/basic/vitest.config.ts +33 -0
  41. package/templates/basic/vitest.e2e.config.ts +28 -0
  42. package/templates/basic/vitest.integration.config.ts +25 -0
  43. package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
  44. package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +6 -9
  45. package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
  46. package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
  47. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +28 -11
  48. package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +19 -13
  49. package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
  50. package/templates/vercel-ai-quickstart/app/globals.css +24 -9
  51. package/templates/vercel-ai-quickstart/app/page.tsx +25 -13
  52. package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
  53. package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
  54. package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
  55. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +41 -14
  56. package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
  57. package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +3 -3
  58. package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
  59. package/templates/vercel-ai-quickstart/next.config.js +10 -2
  60. package/templates/vercel-ai-quickstart/package.json +18 -11
  61. package/templates/vercel-ai-quickstart/test-api.mjs +131 -100
  62. package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +73 -44
  63. package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
  64. package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
  65. package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
  66. package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
@@ -13,7 +13,8 @@ export async function GET(req: Request) {
13
13
  const { searchParams } = new URL(req.url);
14
14
  const conversationId = searchParams.get("conversationId");
15
15
  const userId = searchParams.get("userId");
16
- const memorySpaceId = searchParams.get("memorySpaceId") || "quickstart-demo";
16
+ const memorySpaceId =
17
+ searchParams.get("memorySpaceId") || "quickstart-demo";
17
18
 
18
19
  const cortex = getCortex();
19
20
 
@@ -27,7 +28,7 @@ export async function GET(req: Request) {
27
28
  if (!conversation) {
28
29
  return Response.json(
29
30
  { error: "Conversation not found" },
30
- { status: 404 }
31
+ { status: 404 },
31
32
  );
32
33
  }
33
34
 
@@ -42,7 +43,9 @@ export async function GET(req: Request) {
42
43
  return Response.json({
43
44
  conversation: {
44
45
  id: conversation.conversationId,
45
- title: (conversation.metadata?.title as string) || getDefaultTitle(conversation),
46
+ title:
47
+ (conversation.metadata?.title as string) ||
48
+ getDefaultTitle(conversation),
46
49
  createdAt: conversation.createdAt,
47
50
  updatedAt: conversation.updatedAt,
48
51
  messageCount: conversation.messageCount || 0,
@@ -53,10 +56,7 @@ export async function GET(req: Request) {
53
56
 
54
57
  // List conversations for user (requires userId)
55
58
  if (!userId) {
56
- return Response.json(
57
- { error: "userId is required" },
58
- { status: 400 }
59
- );
59
+ return Response.json({ error: "userId is required" }, { status: 400 });
60
60
  }
61
61
 
62
62
  // Get conversations for the user
@@ -84,7 +84,7 @@ export async function GET(req: Request) {
84
84
 
85
85
  return Response.json(
86
86
  { error: "Failed to fetch conversations" },
87
- { status: 500 }
87
+ { status: 500 },
88
88
  );
89
89
  }
90
90
  }
@@ -95,10 +95,7 @@ export async function POST(req: Request) {
95
95
  const { userId, memorySpaceId = "quickstart-demo", title } = body;
96
96
 
97
97
  if (!userId) {
98
- return Response.json(
99
- { error: "userId is required" },
100
- { status: 400 }
101
- );
98
+ return Response.json({ error: "userId is required" }, { status: 400 });
102
99
  }
103
100
 
104
101
  const cortex = getCortex();
@@ -134,7 +131,7 @@ export async function POST(req: Request) {
134
131
 
135
132
  return Response.json(
136
133
  { error: "Failed to create conversation" },
137
- { status: 500 }
134
+ { status: 500 },
138
135
  );
139
136
  }
140
137
  }
@@ -147,7 +144,7 @@ export async function DELETE(req: Request) {
147
144
  if (!conversationId) {
148
145
  return Response.json(
149
146
  { error: "conversationId is required" },
150
- { status: 400 }
147
+ { status: 400 },
151
148
  );
152
149
  }
153
150
 
@@ -161,7 +158,7 @@ export async function DELETE(req: Request) {
161
158
 
162
159
  return Response.json(
163
160
  { error: "Failed to delete conversation" },
164
- { status: 500 }
161
+ { status: 500 },
165
162
  );
166
163
  }
167
164
  }
@@ -169,7 +166,10 @@ export async function DELETE(req: Request) {
169
166
  /**
170
167
  * Generate a default title from conversation data
171
168
  */
172
- function getDefaultTitle(conv: { createdAt: number; messageCount?: number }): string {
169
+ function getDefaultTitle(conv: {
170
+ createdAt: number;
171
+ messageCount?: number;
172
+ }): string {
173
173
  const date = new Date(conv.createdAt);
174
174
  const timeStr = date.toLocaleTimeString("en-US", {
175
175
  hour: "numeric",
@@ -129,7 +129,8 @@
129
129
  }
130
130
 
131
131
  @keyframes logo-pulse {
132
- 0%, 100% {
132
+ 0%,
133
+ 100% {
133
134
  transform: scale(1);
134
135
  box-shadow: 0 0 0 0 rgba(12, 140, 230, 0.4);
135
136
  }
@@ -182,12 +183,24 @@
182
183
  }
183
184
 
184
185
  /* Staggered animation for conversation list */
185
- .conversation-item:nth-child(1) { animation-delay: 0ms; }
186
- .conversation-item:nth-child(2) { animation-delay: 30ms; }
187
- .conversation-item:nth-child(3) { animation-delay: 60ms; }
188
- .conversation-item:nth-child(4) { animation-delay: 90ms; }
189
- .conversation-item:nth-child(5) { animation-delay: 120ms; }
190
- .conversation-item:nth-child(n+6) { animation-delay: 150ms; }
186
+ .conversation-item:nth-child(1) {
187
+ animation-delay: 0ms;
188
+ }
189
+ .conversation-item:nth-child(2) {
190
+ animation-delay: 30ms;
191
+ }
192
+ .conversation-item:nth-child(3) {
193
+ animation-delay: 60ms;
194
+ }
195
+ .conversation-item:nth-child(4) {
196
+ animation-delay: 90ms;
197
+ }
198
+ .conversation-item:nth-child(5) {
199
+ animation-delay: 120ms;
200
+ }
201
+ .conversation-item:nth-child(n + 6) {
202
+ animation-delay: 150ms;
203
+ }
191
204
 
192
205
  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
193
206
  Input Focus Animations
@@ -216,7 +229,9 @@ input:focus {
216
229
  background: rgba(255, 255, 255, 0.1);
217
230
  border-radius: 50%;
218
231
  transform: translate(-50%, -50%);
219
- transition: width 0.4s ease, height 0.4s ease;
232
+ transition:
233
+ width 0.4s ease,
234
+ height 0.4s ease;
220
235
  }
221
236
 
222
237
  .btn-primary:hover::after {
@@ -265,7 +280,7 @@ input:focus {
265
280
  transform: translateX(-100%);
266
281
  transition: transform 0.3s ease;
267
282
  }
268
-
283
+
269
284
  .sidebar-mobile-visible {
270
285
  transform: translateX(0);
271
286
  position: fixed;
@@ -52,7 +52,9 @@ const ChatHistorySidebar = dynamic(
52
52
  function MainContent() {
53
53
  const { isLoading, isAdminSetup, isAuthenticated, user } = useAuth();
54
54
  const [memorySpaceId, setMemorySpaceId] = useState("quickstart-demo");
55
- const [currentConversationId, setCurrentConversationId] = useState<string | null>(null);
55
+ const [currentConversationId, setCurrentConversationId] = useState<
56
+ string | null
57
+ >(null);
56
58
  const [versions, setVersions] = useState<VersionInfo | null>(null);
57
59
  const {
58
60
  layers,
@@ -74,18 +76,24 @@ function MainContent() {
74
76
  }, [resetLayers]);
75
77
 
76
78
  // Handle conversation selection
77
- const handleSelectConversation = useCallback((conversationId: string) => {
78
- setCurrentConversationId(conversationId);
79
- resetLayers();
80
- }, [resetLayers]);
79
+ const handleSelectConversation = useCallback(
80
+ (conversationId: string) => {
81
+ setCurrentConversationId(conversationId);
82
+ resetLayers();
83
+ },
84
+ [resetLayers],
85
+ );
81
86
 
82
87
  // 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]);
88
+ const handleConversationUpdate = useCallback(
89
+ (conversationId: string) => {
90
+ // Update current conversation ID if it was null (new chat created)
91
+ if (!currentConversationId) {
92
+ setCurrentConversationId(conversationId);
93
+ }
94
+ },
95
+ [currentConversationId],
96
+ );
89
97
 
90
98
  // Loading state
91
99
  if (isLoading) {
@@ -158,7 +166,9 @@ function MainContent() {
158
166
  memorySpaceId={memorySpaceId}
159
167
  userId={userId}
160
168
  conversationId={currentConversationId}
161
- apiEndpoint={versions?.aiSdkMajor === 6 ? "/api/chat-v6" : "/api/chat"}
169
+ apiEndpoint={
170
+ versions?.aiSdkMajor === 6 ? "/api/chat-v6" : "/api/chat"
171
+ }
162
172
  onOrchestrationStart={startOrchestration}
163
173
  onLayerUpdate={updateLayer}
164
174
  onReset={resetLayers}
@@ -193,7 +203,9 @@ function MainContent() {
193
203
  <footer className="border-t border-white/10 px-6 py-3">
194
204
  <div className="flex items-center justify-between text-sm text-gray-500">
195
205
  <div className="flex items-center gap-4">
196
- <span>Cortex SDK {versions ? `v${versions.cortexSdk}` : "..."}</span>
206
+ <span>
207
+ Cortex SDK {versions ? `v${versions.cortexSdk}` : "..."}
208
+ </span>
197
209
  <span>•</span>
198
210
  <span>Vercel AI SDK {versions?.aiSdk ?? "..."}</span>
199
211
  {versions?.aiSdkMajor === 6 && (
@@ -41,7 +41,9 @@ export function AdminSetup() {
41
41
  <span className="text-4xl">🧠</span>
42
42
  </div>
43
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>
44
+ <p className="text-gray-400">
45
+ Set up your admin password to get started
46
+ </p>
45
47
  </div>
46
48
 
47
49
  {/* Setup Form */}
@@ -32,7 +32,7 @@ export interface AuthContextValue extends AuthState {
32
32
  register: (
33
33
  username: string,
34
34
  password: string,
35
- displayName?: string
35
+ displayName?: string,
36
36
  ) => Promise<boolean>;
37
37
  logout: () => void;
38
38
  clearError: () => void;
@@ -164,7 +164,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
164
164
  JSON.stringify({
165
165
  user: data.user,
166
166
  sessionToken: data.sessionToken,
167
- })
167
+ }),
168
168
  );
169
169
  }
170
170
 
@@ -184,7 +184,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
184
184
  return false;
185
185
  }
186
186
  },
187
- []
187
+ [],
188
188
  );
189
189
 
190
190
  // Register
@@ -192,7 +192,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
192
192
  async (
193
193
  username: string,
194
194
  password: string,
195
- displayName?: string
195
+ displayName?: string,
196
196
  ): Promise<boolean> => {
197
197
  setState((prev) => ({ ...prev, error: null }));
198
198
 
@@ -217,7 +217,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
217
217
  JSON.stringify({
218
218
  user: data.user,
219
219
  sessionToken: data.sessionToken,
220
- })
220
+ }),
221
221
  );
222
222
  }
223
223
 
@@ -237,7 +237,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
237
237
  return false;
238
238
  }
239
239
  },
240
- []
240
+ [],
241
241
  );
242
242
 
243
243
  // Logout
@@ -26,9 +26,15 @@ interface ChatHistorySidebarProps {
26
26
  // Helpers
27
27
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
28
28
 
29
- function groupByDate(conversations: Conversation[]): Record<string, Conversation[]> {
29
+ function groupByDate(
30
+ conversations: Conversation[],
31
+ ): Record<string, Conversation[]> {
30
32
  const now = new Date();
31
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
33
+ const today = new Date(
34
+ now.getFullYear(),
35
+ now.getMonth(),
36
+ now.getDate(),
37
+ ).getTime();
32
38
  const yesterday = today - 86400000;
33
39
  const weekAgo = today - 7 * 86400000;
34
40
 
@@ -44,7 +50,7 @@ function groupByDate(conversations: Conversation[]): Record<string, Conversation
44
50
  const convDay = new Date(
45
51
  convDate.getFullYear(),
46
52
  convDate.getMonth(),
47
- convDate.getDate()
53
+ convDate.getDate(),
48
54
  ).getTime();
49
55
 
50
56
  if (convDay >= today) {
@@ -89,7 +95,7 @@ export function ChatHistorySidebar({
89
95
 
90
96
  try {
91
97
  const response = await fetch(
92
- `/api/conversations?userId=${encodeURIComponent(user.id)}&memorySpaceId=${encodeURIComponent(memorySpaceId)}`
98
+ `/api/conversations?userId=${encodeURIComponent(user.id)}&memorySpaceId=${encodeURIComponent(memorySpaceId)}`,
93
99
  );
94
100
  const data = await response.json();
95
101
  if (data.conversations) {
@@ -121,13 +127,18 @@ export function ChatHistorySidebar({
121
127
  setDeletingId(conversationId);
122
128
 
123
129
  try {
124
- const response = await fetch(`/api/conversations?conversationId=${encodeURIComponent(conversationId)}`, {
125
- method: "DELETE",
126
- });
130
+ const response = await fetch(
131
+ `/api/conversations?conversationId=${encodeURIComponent(conversationId)}`,
132
+ {
133
+ method: "DELETE",
134
+ },
135
+ );
127
136
 
128
137
  if (!response.ok) {
129
138
  const data = await response.json().catch(() => ({}));
130
- throw new Error(data.error || `Delete failed with status ${response.status}`);
139
+ throw new Error(
140
+ data.error || `Delete failed with status ${response.status}`,
141
+ );
131
142
  }
132
143
 
133
144
  // Remove from local state only after successful deletion
@@ -69,6 +69,9 @@ export function ChatInterface({
69
69
  conversationIdRef.current = conversationId;
70
70
  }, [conversationId]);
71
71
 
72
+ // Track if we should skip history load (when we create a new conversation ourselves)
73
+ const skipHistoryLoadRef = useRef<string | null>(null);
74
+
72
75
  // Create transport with a function that reads from ref for conversationId
73
76
  // This ensures we always send the latest conversationId
74
77
  const transport = useMemo(
@@ -104,6 +107,8 @@ export function ChatInterface({
104
107
  // Handle conversation title update
105
108
  if (part.type === "data-conversation-update") {
106
109
  const update = part.data as { conversationId: string; title: string };
110
+ // Mark this conversation ID to skip history load - we just created it
111
+ skipHistoryLoadRef.current = update.conversationId;
107
112
  onConversationUpdate?.(update.conversationId, update.title);
108
113
  }
109
114
  },
@@ -122,20 +127,28 @@ export function ChatInterface({
122
127
 
123
128
  // Load messages when conversation changes
124
129
  useEffect(() => {
125
- // Clear messages first
126
- setMessages([]);
127
-
128
- // If no conversation selected, nothing more to do
130
+ // If no conversation selected, just clear messages
129
131
  if (!conversationId) {
132
+ setMessages([]);
133
+ return;
134
+ }
135
+
136
+ // Skip loading if we just created this conversation ourselves
137
+ // (we already have the messages in state from the current chat session)
138
+ if (skipHistoryLoadRef.current === conversationId) {
139
+ skipHistoryLoadRef.current = null;
130
140
  return;
131
141
  }
132
142
 
143
+ // Clear messages before loading new conversation
144
+ setMessages([]);
145
+
133
146
  // Fetch conversation history
134
147
  const loadConversationHistory = async () => {
135
148
  setIsLoadingHistory(true);
136
149
  try {
137
150
  const response = await fetch(
138
- `/api/conversations?conversationId=${encodeURIComponent(conversationId)}`
151
+ `/api/conversations?conversationId=${encodeURIComponent(conversationId)}`,
139
152
  );
140
153
 
141
154
  if (!response.ok) {
@@ -147,12 +160,19 @@ export function ChatInterface({
147
160
 
148
161
  if (data.messages && data.messages.length > 0) {
149
162
  // Transform to the format expected by useChat
150
- const loadedMessages = data.messages.map((msg: { id: string; role: string; content: string; createdAt: string }) => ({
151
- id: msg.id,
152
- role: msg.role,
153
- content: msg.content,
154
- createdAt: new Date(msg.createdAt),
155
- }));
163
+ const loadedMessages = data.messages.map(
164
+ (msg: {
165
+ id: string;
166
+ role: string;
167
+ content: string;
168
+ createdAt: string;
169
+ }) => ({
170
+ id: msg.id,
171
+ role: msg.role,
172
+ content: msg.content,
173
+ createdAt: new Date(msg.createdAt),
174
+ }),
175
+ );
156
176
  setMessages(loadedMessages);
157
177
  }
158
178
  } catch (error) {
@@ -193,7 +213,10 @@ export function ChatInterface({
193
213
  };
194
214
 
195
215
  // Extract text content from message parts (AI SDK v5 format)
196
- const getMessageContent = (message: { content?: string; parts?: Array<{ type: string; text?: string }> }): string => {
216
+ const getMessageContent = (message: {
217
+ content?: string;
218
+ parts?: Array<{ type: string; text?: string }>;
219
+ }): string => {
197
220
  if (typeof message.content === "string") {
198
221
  return message.content;
199
222
  }
@@ -233,7 +256,9 @@ export function ChatInterface({
233
256
  d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
234
257
  />
235
258
  </svg>
236
- <span className="text-gray-400 text-sm">Loading conversation...</span>
259
+ <span className="text-gray-400 text-sm">
260
+ Loading conversation...
261
+ </span>
237
262
  </div>
238
263
  </div>
239
264
  )}
@@ -244,7 +269,9 @@ export function ChatInterface({
244
269
  <span className="text-3xl">🧠</span>
245
270
  </div>
246
271
  <h2 className="text-xl font-semibold mb-2">
247
- {conversationId ? "Continue your conversation" : "Start a new conversation"}
272
+ {conversationId
273
+ ? "Continue your conversation"
274
+ : "Start a new conversation"}
248
275
  </h2>
249
276
  <p className="text-gray-400 max-w-md mx-auto mb-6">
250
277
  This demo shows how Cortex orchestrates memory across multiple
@@ -57,7 +57,9 @@ export function LoginScreen() {
57
57
  <span className="text-4xl">🧠</span>
58
58
  </div>
59
59
  <h1 className="text-3xl font-bold mb-2">Cortex Memory Demo</h1>
60
- <p className="text-gray-400">Sign in to explore AI with long-term memory</p>
60
+ <p className="text-gray-400">
61
+ Sign in to explore AI with long-term memory
62
+ </p>
61
63
  </div>
62
64
 
63
65
  {/* Login/Register Card */}
@@ -114,8 +116,7 @@ export function LoginScreen() {
114
116
  htmlFor="displayName"
115
117
  className="block text-sm font-medium text-gray-300 mb-2"
116
118
  >
117
- Display Name{" "}
118
- <span className="text-gray-500">(optional)</span>
119
+ Display Name <span className="text-gray-500">(optional)</span>
119
120
  </label>
120
121
  <input
121
122
  id="displayName"
@@ -144,7 +145,9 @@ export function LoginScreen() {
144
145
  placeholder="Enter your password"
145
146
  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"
146
147
  disabled={isLoading}
147
- autoComplete={activeTab === "login" ? "current-password" : "new-password"}
148
+ autoComplete={
149
+ activeTab === "login" ? "current-password" : "new-password"
150
+ }
148
151
  />
149
152
  </div>
150
153
 
@@ -181,7 +184,9 @@ export function LoginScreen() {
181
184
  d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
182
185
  />
183
186
  </svg>
184
- {activeTab === "login" ? "Signing in..." : "Creating account..."}
187
+ {activeTab === "login"
188
+ ? "Signing in..."
189
+ : "Creating account..."}
185
190
  </>
186
191
  ) : activeTab === "login" ? (
187
192
  "Sign In"
@@ -99,10 +99,10 @@ export const memoryAgent = new ToolLoopAgent({
99
99
  // └─────────────────────────────────────────────────────────────────┘
100
100
  prepareCall: createMemoryPrepareCall({
101
101
  convexUrl: process.env.CONVEX_URL!,
102
- maxMemories: 20, // Max items to inject from recall
103
- includeFacts: true, // Include Layer 3 facts
102
+ maxMemories: 20, // Max items to inject from recall
103
+ includeFacts: true, // Include Layer 3 facts
104
104
  includeVector: true, // Include Layer 2 vector memories
105
- includeGraph: true, // Expand through graph relationships
105
+ includeGraph: true, // Expand through graph relationships
106
106
  }),
107
107
 
108
108
  // Default to 5 steps (sufficient for most chat interactions)
@@ -28,7 +28,7 @@ export async function hashPassword(password: string): Promise<string> {
28
28
  passwordBuffer,
29
29
  "PBKDF2",
30
30
  false,
31
- ["deriveBits"]
31
+ ["deriveBits"],
32
32
  );
33
33
 
34
34
  // Derive key using PBKDF2
@@ -40,7 +40,7 @@ export async function hashPassword(password: string): Promise<string> {
40
40
  hash: "SHA-256",
41
41
  },
42
42
  keyMaterial,
43
- KEY_LENGTH
43
+ KEY_LENGTH,
44
44
  );
45
45
 
46
46
  // Convert to base64 strings
@@ -60,7 +60,7 @@ export async function hashPassword(password: string): Promise<string> {
60
60
  */
61
61
  export async function verifyPassword(
62
62
  password: string,
63
- storedHash: string
63
+ storedHash: string,
64
64
  ): Promise<boolean> {
65
65
  try {
66
66
  const [saltB64, expectedHashB64] = storedHash.split(":");
@@ -84,7 +84,7 @@ export async function verifyPassword(
84
84
  passwordBuffer,
85
85
  "PBKDF2",
86
86
  false,
87
- ["deriveBits"]
87
+ ["deriveBits"],
88
88
  );
89
89
 
90
90
  // Derive key using same parameters
@@ -96,7 +96,7 @@ export async function verifyPassword(
96
96
  hash: "SHA-256",
97
97
  },
98
98
  keyMaterial,
99
- KEY_LENGTH
99
+ KEY_LENGTH,
100
100
  );
101
101
 
102
102
  // Compare hashes
@@ -4,6 +4,11 @@ const path = require("path");
4
4
  const nextConfig = {
5
5
  transpilePackages: ["@cortexmemory/sdk", "@cortexmemory/vercel-ai-provider"],
6
6
  serverExternalPackages: ["convex"],
7
+ // Disable image optimization to avoid sharp dependency (LGPL licensed)
8
+ // This quickstart doesn't use image optimization features
9
+ images: {
10
+ unoptimized: true,
11
+ },
7
12
  experimental: {
8
13
  // Ensure linked packages resolve dependencies from this project's node_modules
9
14
  externalDir: true,
@@ -16,8 +21,11 @@ const nextConfig = {
16
21
  webpack: (config) => {
17
22
  config.resolve.alias = {
18
23
  ...config.resolve.alias,
19
- "@anthropic-ai/sdk": path.resolve(__dirname, "node_modules/@anthropic-ai/sdk"),
20
- "openai": path.resolve(__dirname, "node_modules/openai"),
24
+ "@anthropic-ai/sdk": path.resolve(
25
+ __dirname,
26
+ "node_modules/@anthropic-ai/sdk",
27
+ ),
28
+ openai: path.resolve(__dirname, "node_modules/openai"),
21
29
  "neo4j-driver": path.resolve(__dirname, "node_modules/neo4j-driver"),
22
30
  };
23
31
  return config;
@@ -5,9 +5,9 @@
5
5
  "license": "UNLICENSED",
6
6
  "description": "Cortex Memory + Vercel AI SDK Quickstart Demo",
7
7
  "scripts": {
8
- "dev": "next dev --webpack",
8
+ "dev": "next dev --webpack -H 0.0.0.0",
9
9
  "build": "next build --webpack",
10
- "start": "next start",
10
+ "start": "next start -H 0.0.0.0",
11
11
  "lint": "next lint",
12
12
  "test": "jest --selectProjects unit integration",
13
13
  "test:unit": "jest --selectProjects unit",
@@ -19,32 +19,39 @@
19
19
  "convex:deploy": "convex deploy"
20
20
  },
21
21
  "dependencies": {
22
- "@ai-sdk/openai": "^3.0.1",
23
- "@ai-sdk/react": "^3.0.3",
22
+ "@ai-sdk/openai": "^3.0.4",
23
+ "@ai-sdk/react": "^3.0.11",
24
24
  "@anthropic-ai/sdk": "^0.71.2",
25
25
  "@cortexmemory/sdk": "file:../../..",
26
26
  "@cortexmemory/vercel-ai-provider": "file:..",
27
- "ai": "^6.0.3",
27
+ "ai": "^6.0.11",
28
28
  "convex": "^1.31.2",
29
- "framer-motion": "^12.23.26",
29
+ "framer-motion": "^12.24.0",
30
30
  "neo4j-driver": "^6.0.1",
31
31
  "next": "^16.1.1",
32
32
  "openai": "^6.15.0",
33
33
  "react": "^19.2.3",
34
34
  "react-dom": "^19.2.3",
35
- "zod": "^4.2.1"
35
+ "zod": "^4.3.5"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@tailwindcss/postcss": "^4.1.18",
39
- "@types/jest": "^29.5.14",
39
+ "@types/jest": "^30.0.0",
40
40
  "@types/node": "^25.0.3",
41
41
  "@types/react": "^19.2.7",
42
42
  "@types/react-dom": "^19.2.3",
43
43
  "autoprefixer": "^10.4.23",
44
- "jest": "^29.7.0",
44
+ "jest": "^30.2.0",
45
45
  "postcss": "^8.5.6",
46
46
  "tailwindcss": "^4.1.18",
47
- "ts-jest": "^29.2.5",
47
+ "ts-jest": "^29.4.6",
48
48
  "typescript": "^5.9.3"
49
- }
49
+ },
50
+ "main": "jest.config.js",
51
+ "directories": {
52
+ "lib": "lib",
53
+ "test": "tests"
54
+ },
55
+ "keywords": [],
56
+ "author": ""
50
57
  }