@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,280 @@
1
+ /**
2
+ * Cortex Memory - HTTP Server Mode
3
+ *
4
+ * REST API server for demonstrating Cortex Memory SDK.
5
+ * Useful for testing with tools like curl, Postman, or integrating with other apps.
6
+ *
7
+ * Usage:
8
+ * npm run server
9
+ *
10
+ * Endpoints:
11
+ * POST /chat Chat and store memory
12
+ * GET /recall Search memories
13
+ * GET /facts List stored facts
14
+ * GET /history/:id Get conversation history
15
+ * GET /health Health check
16
+ */
17
+
18
+ import { serve } from "@hono/node-server";
19
+ import { Hono } from "hono";
20
+ import { cors } from "hono/cors";
21
+ import { logger } from "hono/logger";
22
+ import { closeCortex, CONFIG } from "./cortex.js";
23
+ import {
24
+ chat,
25
+ recallMemories,
26
+ listFacts,
27
+ generateConversationId,
28
+ } from "./chat.js";
29
+ import { printWelcome, printInfo, printError } from "./display.js";
30
+
31
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
32
+ // Server Setup
33
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
34
+
35
+ const app = new Hono();
36
+
37
+ // Middleware
38
+ app.use("*", cors());
39
+ app.use("*", logger());
40
+
41
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
42
+ // Routes
43
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
44
+
45
+ /**
46
+ * Health check
47
+ */
48
+ app.get("/health", (c) => {
49
+ return c.json({
50
+ status: "ok",
51
+ memorySpaceId: CONFIG.memorySpaceId,
52
+ agentId: CONFIG.agentId,
53
+ features: {
54
+ factExtraction: CONFIG.enableFactExtraction,
55
+ graphSync: CONFIG.enableGraphMemory,
56
+ llm: !!process.env.OPENAI_API_KEY,
57
+ },
58
+ });
59
+ });
60
+
61
+ /**
62
+ * Chat endpoint
63
+ *
64
+ * POST /chat
65
+ * Body: { message: string, conversationId?: string }
66
+ */
67
+ app.post("/chat", async (c) => {
68
+ try {
69
+ const body = await c.req.json();
70
+ const { message, conversationId } = body;
71
+
72
+ if (!message || typeof message !== "string") {
73
+ return c.json({ error: "message is required" }, 400);
74
+ }
75
+
76
+ const convId = conversationId || generateConversationId();
77
+
78
+ console.log(`\n[Chat] User: ${message.slice(0, 50)}${message.length > 50 ? "..." : ""}`);
79
+
80
+ const result = await chat(message, convId);
81
+
82
+ console.log(`[Chat] Response sent (${result.memoriesRecalled} memories, ${result.factsRecalled} facts recalled)\n`);
83
+
84
+ return c.json({
85
+ response: result.response,
86
+ conversationId: result.conversationId,
87
+ memoriesRecalled: result.memoriesRecalled,
88
+ factsRecalled: result.factsRecalled,
89
+ });
90
+ } catch (error) {
91
+ const message = error instanceof Error ? error.message : "Unknown error";
92
+ printError("Chat failed", error instanceof Error ? error : undefined);
93
+ return c.json({ error: message }, 500);
94
+ }
95
+ });
96
+
97
+ /**
98
+ * Recall endpoint
99
+ *
100
+ * GET /recall?query=<query>
101
+ */
102
+ app.get("/recall", async (c) => {
103
+ try {
104
+ const query = c.req.query("query");
105
+
106
+ if (!query) {
107
+ return c.json({ error: "query parameter is required" }, 400);
108
+ }
109
+
110
+ console.log(`\n[Recall] Query: ${query}`);
111
+
112
+ // Use the internal recall function but capture results
113
+ const { getCortex } = await import("./cortex.js");
114
+ const cortex = getCortex();
115
+
116
+ const result = await cortex.memory.recall({
117
+ memorySpaceId: CONFIG.memorySpaceId,
118
+ query,
119
+ limit: 10,
120
+ sources: {
121
+ vector: true,
122
+ facts: true,
123
+ graph: CONFIG.enableGraphMemory,
124
+ },
125
+ });
126
+
127
+ console.log(`[Recall] Found ${result.memories?.length || 0} memories, ${result.facts?.length || 0} facts\n`);
128
+
129
+ return c.json({
130
+ memories: result.memories || [],
131
+ facts: result.facts || [],
132
+ query,
133
+ });
134
+ } catch (error) {
135
+ const message = error instanceof Error ? error.message : "Unknown error";
136
+ return c.json({ error: message }, 500);
137
+ }
138
+ });
139
+
140
+ /**
141
+ * Facts endpoint
142
+ *
143
+ * GET /facts
144
+ */
145
+ app.get("/facts", async (c) => {
146
+ try {
147
+ const { getCortex } = await import("./cortex.js");
148
+ const cortex = getCortex();
149
+
150
+ const result = await cortex.facts.list({
151
+ memorySpaceId: CONFIG.memorySpaceId,
152
+ limit: 50,
153
+ });
154
+
155
+ const facts = result.facts || result || [];
156
+
157
+ return c.json({ facts, count: facts.length });
158
+ } catch (error) {
159
+ const message = error instanceof Error ? error.message : "Unknown error";
160
+ return c.json({ error: message }, 500);
161
+ }
162
+ });
163
+
164
+ /**
165
+ * Conversation history endpoint
166
+ *
167
+ * GET /history/:conversationId
168
+ */
169
+ app.get("/history/:conversationId", async (c) => {
170
+ try {
171
+ const conversationId = c.req.param("conversationId");
172
+
173
+ const { getCortex } = await import("./cortex.js");
174
+ const cortex = getCortex();
175
+
176
+ const conversation = await cortex.conversations.get(conversationId);
177
+
178
+ if (!conversation) {
179
+ return c.json({ error: "Conversation not found" }, 404);
180
+ }
181
+
182
+ return c.json({
183
+ conversationId,
184
+ messages: conversation.messages || [],
185
+ messageCount: conversation.messages?.length || 0,
186
+ });
187
+ } catch (error) {
188
+ const message = error instanceof Error ? error.message : "Unknown error";
189
+ return c.json({ error: message }, 500);
190
+ }
191
+ });
192
+
193
+ /**
194
+ * Root endpoint - API docs
195
+ */
196
+ app.get("/", (c) => {
197
+ return c.json({
198
+ name: "Cortex Memory - Basic Demo API",
199
+ version: "1.0.0",
200
+ endpoints: {
201
+ "POST /chat": {
202
+ description: "Chat and store memory",
203
+ body: { message: "string", conversationId: "string (optional)" },
204
+ },
205
+ "GET /recall": {
206
+ description: "Search memories",
207
+ query: { query: "string" },
208
+ },
209
+ "GET /facts": {
210
+ description: "List all stored facts",
211
+ },
212
+ "GET /history/:id": {
213
+ description: "Get conversation history",
214
+ },
215
+ "GET /health": {
216
+ description: "Health check",
217
+ },
218
+ },
219
+ });
220
+ });
221
+
222
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
223
+ // Server Start
224
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
225
+
226
+ const PORT = parseInt(process.env.PORT || "3001", 10);
227
+
228
+ async function main(): Promise<void> {
229
+ // Check for required environment
230
+ if (!process.env.CONVEX_URL) {
231
+ printError(
232
+ "CONVEX_URL is required. Set it in .env.local or run: cortex init",
233
+ );
234
+ process.exit(1);
235
+ }
236
+
237
+ // Print welcome
238
+ printWelcome("server");
239
+
240
+ // Start server
241
+ console.log(`🚀 Server starting on http://localhost:${PORT}`);
242
+ console.log("");
243
+ console.log("Endpoints:");
244
+ console.log(` POST http://localhost:${PORT}/chat`);
245
+ console.log(` GET http://localhost:${PORT}/recall?query=...`);
246
+ console.log(` GET http://localhost:${PORT}/facts`);
247
+ console.log(` GET http://localhost:${PORT}/history/:id`);
248
+ console.log(` GET http://localhost:${PORT}/health`);
249
+ console.log("");
250
+ console.log("Example:");
251
+ console.log(` curl -X POST http://localhost:${PORT}/chat \\`);
252
+ console.log(' -H "Content-Type: application/json" \\');
253
+ console.log(' -d \'{"message": "My name is Alex"}\'');
254
+ console.log("");
255
+
256
+ serve({
257
+ fetch: app.fetch,
258
+ port: PORT,
259
+ });
260
+
261
+ printInfo(`Server running on port ${PORT}`);
262
+ }
263
+
264
+ // Handle cleanup
265
+ process.on("SIGINT", () => {
266
+ console.log("\n\nShutting down...");
267
+ closeCortex();
268
+ process.exit(0);
269
+ });
270
+
271
+ process.on("SIGTERM", () => {
272
+ closeCortex();
273
+ process.exit(0);
274
+ });
275
+
276
+ // Run
277
+ main().catch((error) => {
278
+ printError("Fatal error", error instanceof Error ? error : undefined);
279
+ process.exit(1);
280
+ });
@@ -0,0 +1,33 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ include: [
8
+ "src/**/*.test.ts",
9
+ "src/__tests__/**/*.test.ts",
10
+ ],
11
+ // Unit tests run fast
12
+ testTimeout: 10000,
13
+ coverage: {
14
+ provider: "v8",
15
+ reporter: ["text", "json", "html"],
16
+ include: ["src/**/*.ts"],
17
+ exclude: [
18
+ "src/**/*.test.ts",
19
+ "src/__tests__/**",
20
+ "src/index.ts", // Entry point, tested via integration
21
+ "src/server.ts", // Entry point, tested via integration
22
+ ],
23
+ },
24
+ clearMocks: true,
25
+ restoreMocks: true,
26
+ // Separate pools for different test types
27
+ poolOptions: {
28
+ forks: {
29
+ singleFork: true,
30
+ },
31
+ },
32
+ },
33
+ });
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ /**
4
+ * Configuration for E2E tests.
5
+ * These tests require real Convex backend and optionally OpenAI API key.
6
+ *
7
+ * Run with:
8
+ * CONVEX_URL=<url> npm run test:e2e
9
+ * CONVEX_URL=<url> OPENAI_API_KEY=<key> npm run test:e2e
10
+ */
11
+ export default defineConfig({
12
+ test: {
13
+ globals: true,
14
+ environment: "node",
15
+ include: ["src/__tests__/e2e/**/*.test.ts"],
16
+ testTimeout: 180000, // 3 minutes for E2E tests (fact extraction takes time)
17
+ hookTimeout: 60000, // 1 minute for setup/teardown
18
+ clearMocks: true,
19
+ restoreMocks: true,
20
+ // Run E2E tests sequentially to avoid rate limits
21
+ pool: "forks",
22
+ poolOptions: {
23
+ forks: {
24
+ singleFork: true,
25
+ },
26
+ },
27
+ },
28
+ });
@@ -0,0 +1,25 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ /**
4
+ * Configuration for integration tests.
5
+ * These tests use mocked SDK but test real component interactions.
6
+ */
7
+ export default defineConfig({
8
+ test: {
9
+ globals: true,
10
+ environment: "node",
11
+ include: ["src/__tests__/integration/**/*.test.ts"],
12
+ testTimeout: 30000, // 30 seconds for integration tests
13
+ coverage: {
14
+ provider: "v8",
15
+ reporter: ["text", "json", "html"],
16
+ include: ["src/**/*.ts"],
17
+ exclude: [
18
+ "src/**/*.test.ts",
19
+ "src/__tests__/**",
20
+ ],
21
+ },
22
+ clearMocks: true,
23
+ restoreMocks: true,
24
+ },
25
+ });
@@ -24,7 +24,7 @@ export async function GET() {
24
24
 
25
25
  return Response.json(
26
26
  { error: "Failed to check admin setup status" },
27
- { status: 500 }
27
+ { status: 500 },
28
28
  );
29
29
  }
30
30
  }
@@ -7,25 +7,69 @@
7
7
  import { getCortex } from "@/lib/cortex";
8
8
  import { verifyPassword, generateSessionToken } from "@/lib/password";
9
9
 
10
+ /**
11
+ * Validates login request body structure.
12
+ * Returns validated credentials or null if invalid.
13
+ */
14
+ function validateLoginBody(
15
+ body: unknown,
16
+ ): { username: string; password: string } | null {
17
+ if (typeof body !== "object" || body === null) {
18
+ return null;
19
+ }
20
+
21
+ const record = body as Record<string, unknown>;
22
+
23
+ // Validate username field exists and is a non-empty string
24
+ const hasValidUsername =
25
+ "username" in record &&
26
+ typeof record.username === "string" &&
27
+ record.username.length > 0 &&
28
+ record.username.length <= 256;
29
+
30
+ // Validate password field exists and is a non-empty string
31
+ const hasValidPassword =
32
+ "password" in record &&
33
+ typeof record.password === "string" &&
34
+ record.password.length > 0 &&
35
+ record.password.length <= 1024;
36
+
37
+ if (!hasValidUsername || !hasValidPassword) {
38
+ return null;
39
+ }
40
+
41
+ return {
42
+ username: record.username as string,
43
+ password: record.password as string,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Safely extracts an error message for logging without exposing user data.
49
+ */
50
+ function getSafeErrorMessage(error: unknown): string {
51
+ if (error instanceof Error) {
52
+ // Only include error name and a sanitized message
53
+ // Avoid logging full stack traces which may contain user data
54
+ return `${error.name}: ${error.message.slice(0, 200)}`;
55
+ }
56
+ return "Unknown error";
57
+ }
58
+
10
59
  export async function POST(req: Request) {
11
60
  try {
12
61
  const body = await req.json();
13
- const { username, password } = body;
14
62
 
15
- // Validate input
16
- if (!username || typeof username !== "string") {
63
+ // Validate input structure before extracting values
64
+ const credentials = validateLoginBody(body);
65
+ if (!credentials) {
17
66
  return Response.json(
18
- { error: "Username is required" },
19
- { status: 400 }
67
+ { error: "Username and password are required" },
68
+ { status: 400 },
20
69
  );
21
70
  }
22
71
 
23
- if (!password || typeof password !== "string") {
24
- return Response.json(
25
- { error: "Password is required" },
26
- { status: 400 }
27
- );
28
- }
72
+ const { username, password } = credentials;
29
73
 
30
74
  const cortex = getCortex();
31
75
  const sanitizedUsername = username.toLowerCase();
@@ -35,7 +79,7 @@ export async function POST(req: Request) {
35
79
  if (!user) {
36
80
  return Response.json(
37
81
  { error: "Invalid username or password" },
38
- { status: 401 }
82
+ { status: 401 },
39
83
  );
40
84
  }
41
85
 
@@ -44,7 +88,7 @@ export async function POST(req: Request) {
44
88
  if (!storedHash) {
45
89
  return Response.json(
46
90
  { error: "Invalid username or password" },
47
- { status: 401 }
91
+ { status: 401 },
48
92
  );
49
93
  }
50
94
 
@@ -52,7 +96,7 @@ export async function POST(req: Request) {
52
96
  if (!isValid) {
53
97
  return Response.json(
54
98
  { error: "Invalid username or password" },
55
- { status: 401 }
99
+ { status: 401 },
56
100
  );
57
101
  }
58
102
 
@@ -73,11 +117,9 @@ export async function POST(req: Request) {
73
117
  sessionToken,
74
118
  });
75
119
  } catch (error) {
76
- console.error("[Login Error]", error);
120
+ // Log sanitized error to prevent log injection
121
+ console.error("[Login Error]", getSafeErrorMessage(error));
77
122
 
78
- return Response.json(
79
- { error: "Failed to authenticate" },
80
- { status: 500 }
81
- );
123
+ return Response.json({ error: "Failed to authenticate" }, { status: 500 });
82
124
  }
83
125
  }
@@ -14,39 +14,38 @@ export async function POST(req: Request) {
14
14
 
15
15
  // Validate input
16
16
  if (!username || typeof username !== "string") {
17
- return Response.json(
18
- { error: "Username is required" },
19
- { status: 400 }
20
- );
17
+ return Response.json({ error: "Username is required" }, { status: 400 });
21
18
  }
22
19
 
23
20
  if (!password || typeof password !== "string") {
24
- return Response.json(
25
- { error: "Password is required" },
26
- { status: 400 }
27
- );
21
+ return Response.json({ error: "Password is required" }, { status: 400 });
28
22
  }
29
23
 
30
24
  if (username.length < 2) {
31
25
  return Response.json(
32
26
  { error: "Username must be at least 2 characters" },
33
- { status: 400 }
27
+ { status: 400 },
34
28
  );
35
29
  }
36
30
 
37
31
  if (password.length < 4) {
38
32
  return Response.json(
39
33
  { error: "Password must be at least 4 characters" },
40
- { status: 400 }
34
+ { status: 400 },
41
35
  );
42
36
  }
43
37
 
44
38
  // Sanitize username (alphanumeric, underscore, hyphen only)
45
- const sanitizedUsername = username.toLowerCase().replace(/[^a-z0-9_-]/g, "");
39
+ const sanitizedUsername = username
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9_-]/g, "");
46
42
  if (sanitizedUsername !== username.toLowerCase()) {
47
43
  return Response.json(
48
- { error: "Username can only contain letters, numbers, underscores, and hyphens" },
49
- { status: 400 }
44
+ {
45
+ error:
46
+ "Username can only contain letters, numbers, underscores, and hyphens",
47
+ },
48
+ { status: 400 },
50
49
  );
51
50
  }
52
51
 
@@ -57,7 +56,7 @@ export async function POST(req: Request) {
57
56
  if (existingUser) {
58
57
  return Response.json(
59
58
  { error: "Username already taken" },
60
- { status: 409 }
59
+ { status: 409 },
61
60
  );
62
61
  }
63
62
 
@@ -86,9 +85,6 @@ export async function POST(req: Request) {
86
85
  } catch (error) {
87
86
  console.error("[Register Error]", error);
88
87
 
89
- return Response.json(
90
- { error: "Failed to register user" },
91
- { status: 500 }
92
- );
88
+ return Response.json({ error: "Failed to register user" }, { status: 500 });
93
89
  }
94
90
  }
@@ -16,16 +16,13 @@ export async function POST(req: Request) {
16
16
  const { password } = body;
17
17
 
18
18
  if (!password || typeof password !== "string") {
19
- return Response.json(
20
- { error: "Password is required" },
21
- { status: 400 }
22
- );
19
+ return Response.json({ error: "Password is required" }, { status: 400 });
23
20
  }
24
21
 
25
22
  if (password.length < 4) {
26
23
  return Response.json(
27
24
  { error: "Password must be at least 4 characters" },
28
- { status: 400 }
25
+ { status: 400 },
29
26
  );
30
27
  }
31
28
 
@@ -36,7 +33,7 @@ export async function POST(req: Request) {
36
33
  if (existingHash !== null) {
37
34
  return Response.json(
38
35
  { error: "Admin already configured" },
39
- { status: 409 }
36
+ { status: 409 },
40
37
  );
41
38
  }
42
39
 
@@ -53,7 +50,7 @@ export async function POST(req: Request) {
53
50
 
54
51
  return Response.json(
55
52
  { error: "Failed to configure admin password" },
56
- { status: 500 }
53
+ { status: 500 },
57
54
  );
58
55
  }
59
56
  }