@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.
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +18 -6
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +191 -80
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +3 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/app-template-sync.d.ts.map +1 -1
- package/dist/utils/app-template-sync.js +35 -13
- package/dist/utils/app-template-sync.js.map +1 -1
- package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
- package/dist/utils/init/quickstart-setup.js.map +1 -1
- package/package.json +4 -4
- package/templates/basic/.env.local.example +23 -0
- package/templates/basic/README.md +181 -56
- package/templates/basic/package-lock.json +2180 -406
- package/templates/basic/package.json +23 -5
- package/templates/basic/src/__tests__/chat.test.ts +340 -0
- package/templates/basic/src/__tests__/cortex.test.ts +260 -0
- package/templates/basic/src/__tests__/display.test.ts +455 -0
- package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
- package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
- package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
- package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
- package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
- package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
- package/templates/basic/src/__tests__/llm.test.ts +344 -0
- package/templates/basic/src/chat.ts +300 -0
- package/templates/basic/src/cortex.ts +203 -0
- package/templates/basic/src/display.ts +425 -0
- package/templates/basic/src/index.ts +194 -64
- package/templates/basic/src/llm.ts +214 -0
- package/templates/basic/src/server.ts +280 -0
- package/templates/basic/vitest.config.ts +33 -0
- package/templates/basic/vitest.e2e.config.ts +28 -0
- package/templates/basic/vitest.integration.config.ts +25 -0
- package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +61 -19
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +95 -23
- package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +339 -0
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
- package/templates/vercel-ai-quickstart/app/globals.css +24 -9
- package/templates/vercel-ai-quickstart/app/page.tsx +41 -15
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +46 -16
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
- package/templates/vercel-ai-quickstart/jest.config.js +8 -1
- package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +165 -0
- package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
- package/templates/vercel-ai-quickstart/lib/versions.ts +60 -0
- package/templates/vercel-ai-quickstart/next.config.js +10 -2
- package/templates/vercel-ai-quickstart/package.json +23 -12
- package/templates/vercel-ai-quickstart/test-api.mjs +303 -0
- package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +483 -0
- package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
- 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
|
+
}
|