@cortexmemory/cli 0.27.3 → 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 (68) 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 +191 -80
  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 +35 -13
  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 +61 -19
  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 +95 -23
  48. package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +339 -0
  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 +41 -15
  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 +46 -16
  56. package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
  57. package/templates/vercel-ai-quickstart/jest.config.js +8 -1
  58. package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +165 -0
  59. package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
  60. package/templates/vercel-ai-quickstart/lib/versions.ts +60 -0
  61. package/templates/vercel-ai-quickstart/next.config.js +10 -2
  62. package/templates/vercel-ai-quickstart/package.json +23 -12
  63. package/templates/vercel-ai-quickstart/test-api.mjs +303 -0
  64. package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +483 -0
  65. package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
  66. package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
  67. package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
  68. package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Test Utilities for Basic Template Tests
3
+ *
4
+ * Shared helpers for both integration and e2e tests.
5
+ */
6
+
7
+ import crypto from "crypto";
8
+
9
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
10
+ // Environment Checks
11
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+
13
+ /**
14
+ * Check if e2e tests should be skipped (no CONVEX_URL)
15
+ */
16
+ export function shouldSkipE2E(): boolean {
17
+ return !process.env.CONVEX_URL;
18
+ }
19
+
20
+ /**
21
+ * Check if fact extraction tests should be skipped (no OPENAI_API_KEY)
22
+ */
23
+ export function shouldSkipFactTests(): boolean {
24
+ return !process.env.CONVEX_URL || !process.env.OPENAI_API_KEY;
25
+ }
26
+
27
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
28
+ // ID Generation
29
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
30
+
31
+ /**
32
+ * Generate a cryptographically secure random string
33
+ */
34
+ function generateSecureRandomString(length: number): string {
35
+ const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
36
+ const charsLength = chars.length;
37
+ const maxUnbiased = Math.floor(256 / charsLength) * charsLength;
38
+
39
+ let result = "";
40
+ while (result.length < length) {
41
+ const byte = crypto.randomBytes(1)[0];
42
+ if (byte >= maxUnbiased) continue;
43
+ result += chars[byte % charsLength];
44
+ }
45
+ return result;
46
+ }
47
+
48
+ /**
49
+ * Generate unique ID for test isolation
50
+ */
51
+ export function generateTestId(prefix: string = "test"): string {
52
+ const timestamp = Date.now();
53
+ const random = generateSecureRandomString(6);
54
+ return `${prefix}-${timestamp}-${random}`;
55
+ }
56
+
57
+ /**
58
+ * Generate unique memory space ID
59
+ */
60
+ export function createTestMemorySpaceId(prefix: string = "test"): string {
61
+ return generateTestId(`${prefix}-space`);
62
+ }
63
+
64
+ /**
65
+ * Generate unique conversation ID
66
+ */
67
+ export function createTestConversationId(): string {
68
+ return generateTestId("conv-test");
69
+ }
70
+
71
+ /**
72
+ * Generate unique user ID
73
+ */
74
+ export function createTestUserId(): string {
75
+ return generateTestId("user-test");
76
+ }
77
+
78
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79
+ // Mock Cortex SDK
80
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
81
+
82
+ export interface MockMemory {
83
+ memoryId: string;
84
+ content: string;
85
+ importance: number;
86
+ conversationId?: string;
87
+ }
88
+
89
+ export interface MockFact {
90
+ factId: string;
91
+ fact: string;
92
+ factType: string;
93
+ confidence: number;
94
+ }
95
+
96
+ export interface MockConversation {
97
+ conversationId: string;
98
+ messages: Array<{ role: string; content: string }>;
99
+ messageCount: number;
100
+ }
101
+
102
+ /**
103
+ * Create a mock Cortex SDK instance for integration tests
104
+ */
105
+ export function createMockCortex() {
106
+ // Internal state for stateful mocks
107
+ const storedMemories: MockMemory[] = [];
108
+ const storedFacts: MockFact[] = [];
109
+ const storedConversations: Map<string, MockConversation> = new Map();
110
+
111
+ return {
112
+ memory: {
113
+ recall: vi.fn().mockImplementation(async (params: { query?: string }) => {
114
+ return {
115
+ memories: storedMemories,
116
+ facts: storedFacts,
117
+ context: storedMemories.map((m) => m.content).join("\n"),
118
+ totalResults: storedMemories.length + storedFacts.length,
119
+ queryTimeMs: 50,
120
+ };
121
+ }),
122
+ remember: vi.fn().mockImplementation(async (params: {
123
+ conversationId?: string;
124
+ userMessage?: string;
125
+ agentResponse?: string;
126
+ }) => {
127
+ const convId = params.conversationId || generateTestId("conv");
128
+
129
+ // Store memory
130
+ const memory: MockMemory = {
131
+ memoryId: generateTestId("mem"),
132
+ content: params.userMessage || "",
133
+ importance: 50,
134
+ conversationId: convId,
135
+ };
136
+ storedMemories.push(memory);
137
+
138
+ // Update conversation
139
+ let conv = storedConversations.get(convId);
140
+ if (!conv) {
141
+ conv = {
142
+ conversationId: convId,
143
+ messages: [],
144
+ messageCount: 0,
145
+ };
146
+ storedConversations.set(convId, conv);
147
+ }
148
+ conv.messages.push({ role: "user", content: params.userMessage || "" });
149
+ conv.messages.push({ role: "assistant", content: params.agentResponse || "" });
150
+ conv.messageCount = conv.messages.length;
151
+
152
+ return {
153
+ conversation: {
154
+ conversationId: convId,
155
+ messageIds: [`msg-${Date.now()}-1`, `msg-${Date.now()}-2`],
156
+ },
157
+ memories: [memory],
158
+ facts: [],
159
+ };
160
+ }),
161
+ list: vi.fn().mockImplementation(async () => storedMemories),
162
+ },
163
+ facts: {
164
+ list: vi.fn().mockImplementation(async () => ({
165
+ facts: storedFacts,
166
+ })),
167
+ },
168
+ conversations: {
169
+ get: vi.fn().mockImplementation(async (conversationId: string) => {
170
+ return storedConversations.get(conversationId) || null;
171
+ }),
172
+ list: vi.fn().mockImplementation(async () => {
173
+ return Array.from(storedConversations.values());
174
+ }),
175
+ },
176
+ close: vi.fn(),
177
+
178
+ // Test helpers to manipulate state
179
+ __test: {
180
+ addMemory: (memory: MockMemory) => storedMemories.push(memory),
181
+ addFact: (fact: MockFact) => storedFacts.push(fact),
182
+ clearAll: () => {
183
+ storedMemories.length = 0;
184
+ storedFacts.length = 0;
185
+ storedConversations.clear();
186
+ },
187
+ getMemories: () => storedMemories,
188
+ getFacts: () => storedFacts,
189
+ getConversations: () => storedConversations,
190
+ },
191
+ };
192
+ }
193
+
194
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
195
+ // Mock Data Factories
196
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
197
+
198
+ /**
199
+ * Create mock memories for testing
200
+ */
201
+ export function createMockMemories(count: number = 2): MockMemory[] {
202
+ return Array.from({ length: count }, (_, i) => ({
203
+ memoryId: `mem-${i + 1}`,
204
+ content: `Test memory ${i + 1}`,
205
+ importance: 80 - i * 10,
206
+ conversationId: `conv-${i + 1}`,
207
+ }));
208
+ }
209
+
210
+ /**
211
+ * Create mock facts for testing
212
+ */
213
+ export function createMockFacts(count: number = 2): MockFact[] {
214
+ return Array.from({ length: count }, (_, i) => ({
215
+ factId: `fact-${i + 1}`,
216
+ fact: `Test fact ${i + 1}`,
217
+ factType: "knowledge",
218
+ confidence: 90 - i * 5,
219
+ }));
220
+ }
221
+
222
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
223
+ // Async Helpers
224
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
225
+
226
+ /**
227
+ * Wait for a specified time
228
+ */
229
+ export function wait(ms: number): Promise<void> {
230
+ return new Promise((resolve) => setTimeout(resolve, ms));
231
+ }
232
+
233
+ /**
234
+ * Wait for a condition to be true
235
+ */
236
+ export async function waitFor(
237
+ condition: () => boolean | Promise<boolean>,
238
+ timeout: number = 5000,
239
+ interval: number = 100,
240
+ ): Promise<void> {
241
+ const start = Date.now();
242
+ while (Date.now() - start < timeout) {
243
+ if (await condition()) return;
244
+ await wait(interval);
245
+ }
246
+ throw new Error(`Timeout waiting for condition after ${timeout}ms`);
247
+ }
248
+
249
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
250
+ // HTTP Test Helpers
251
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
252
+
253
+ /**
254
+ * Create a mock Request object for testing HTTP endpoints
255
+ */
256
+ export function createTestRequest(
257
+ method: string,
258
+ url: string,
259
+ options: {
260
+ body?: Record<string, unknown>;
261
+ headers?: Record<string, string>;
262
+ } = {},
263
+ ): Request {
264
+ const init: RequestInit = { method };
265
+
266
+ if (options.body) {
267
+ init.body = JSON.stringify(options.body);
268
+ init.headers = {
269
+ "Content-Type": "application/json",
270
+ ...options.headers,
271
+ };
272
+ } else if (options.headers) {
273
+ init.headers = options.headers;
274
+ }
275
+
276
+ return new Request(url, init);
277
+ }
278
+
279
+ /**
280
+ * Parse JSON response from HTTP endpoint
281
+ */
282
+ export async function parseTestResponse(response: Response): Promise<{
283
+ status: number;
284
+ data: Record<string, unknown>;
285
+ }> {
286
+ const data = await response.json();
287
+ return { status: response.status, data };
288
+ }
289
+
290
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
291
+ // E2E Test Helpers
292
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
293
+
294
+ /**
295
+ * Generate OpenAI embedding for e2e tests
296
+ */
297
+ export async function generateTestEmbedding(text: string): Promise<number[]> {
298
+ if (!process.env.OPENAI_API_KEY) {
299
+ throw new Error("OPENAI_API_KEY required for embedding generation");
300
+ }
301
+
302
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
303
+ method: "POST",
304
+ headers: {
305
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
306
+ "Content-Type": "application/json",
307
+ },
308
+ body: JSON.stringify({
309
+ model: "text-embedding-3-small",
310
+ input: text,
311
+ }),
312
+ });
313
+
314
+ const data = await response.json();
315
+ return data.data[0].embedding;
316
+ }
317
+
318
+ /**
319
+ * Make HTTP request to local server for e2e tests
320
+ */
321
+ export async function makeServerRequest(
322
+ path: string,
323
+ options: {
324
+ method?: string;
325
+ body?: Record<string, unknown>;
326
+ baseUrl?: string;
327
+ } = {},
328
+ ): Promise<{ status: number; data: Record<string, unknown> }> {
329
+ const baseUrl = options.baseUrl || "http://localhost:3001";
330
+ const url = `${baseUrl}${path}`;
331
+
332
+ const fetchOptions: RequestInit = {
333
+ method: options.method || "GET",
334
+ };
335
+
336
+ if (options.body) {
337
+ fetchOptions.body = JSON.stringify(options.body);
338
+ fetchOptions.headers = { "Content-Type": "application/json" };
339
+ }
340
+
341
+ const response = await fetch(url, fetchOptions);
342
+ const data = await response.json();
343
+
344
+ return { status: response.status, data };
345
+ }