@cortexmemory/cli 0.1.1 → 0.22.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 (103) hide show
  1. package/README.md +8 -0
  2. package/dist/commands/conversations.d.ts +1 -1
  3. package/dist/commands/conversations.d.ts.map +1 -1
  4. package/dist/commands/conversations.js +57 -27
  5. package/dist/commands/conversations.js.map +1 -1
  6. package/dist/commands/convex.d.ts +1 -1
  7. package/dist/commands/convex.d.ts.map +1 -1
  8. package/dist/commands/convex.js +237 -64
  9. package/dist/commands/convex.js.map +1 -1
  10. package/dist/commands/db.d.ts +1 -1
  11. package/dist/commands/db.d.ts.map +1 -1
  12. package/dist/commands/db.js +59 -109
  13. package/dist/commands/db.js.map +1 -1
  14. package/dist/commands/dev.d.ts +8 -8
  15. package/dist/commands/dev.d.ts.map +1 -1
  16. package/dist/commands/dev.js +734 -513
  17. package/dist/commands/dev.js.map +1 -1
  18. package/dist/commands/facts.d.ts +1 -1
  19. package/dist/commands/facts.d.ts.map +1 -1
  20. package/dist/commands/facts.js +50 -21
  21. package/dist/commands/facts.js.map +1 -1
  22. package/dist/commands/init.d.ts +28 -0
  23. package/dist/commands/init.d.ts.map +1 -0
  24. package/dist/commands/init.js +895 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/memory.d.ts +1 -1
  27. package/dist/commands/memory.d.ts.map +1 -1
  28. package/dist/commands/memory.js +64 -27
  29. package/dist/commands/memory.js.map +1 -1
  30. package/dist/commands/setup.d.ts +4 -5
  31. package/dist/commands/setup.d.ts.map +1 -1
  32. package/dist/commands/setup.js +428 -250
  33. package/dist/commands/setup.js.map +1 -1
  34. package/dist/commands/spaces.d.ts +1 -1
  35. package/dist/commands/spaces.d.ts.map +1 -1
  36. package/dist/commands/spaces.js +100 -43
  37. package/dist/commands/spaces.js.map +1 -1
  38. package/dist/commands/status.d.ts +17 -0
  39. package/dist/commands/status.d.ts.map +1 -0
  40. package/dist/commands/status.js +314 -0
  41. package/dist/commands/status.js.map +1 -0
  42. package/dist/commands/users.d.ts +1 -1
  43. package/dist/commands/users.d.ts.map +1 -1
  44. package/dist/commands/users.js +80 -42
  45. package/dist/commands/users.js.map +1 -1
  46. package/dist/index.js +42 -14
  47. package/dist/index.js.map +1 -1
  48. package/dist/types.d.ts +11 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/utils/__tests__/client.test.d.ts +5 -0
  51. package/dist/utils/__tests__/client.test.d.ts.map +1 -0
  52. package/dist/utils/__tests__/client.test.js +88 -0
  53. package/dist/utils/__tests__/client.test.js.map +1 -0
  54. package/dist/utils/__tests__/env-file.test.d.ts +7 -0
  55. package/dist/utils/__tests__/env-file.test.d.ts.map +1 -0
  56. package/dist/utils/__tests__/env-file.test.js +196 -0
  57. package/dist/utils/__tests__/env-file.test.js.map +1 -0
  58. package/dist/utils/__tests__/shell.test.d.ts +7 -0
  59. package/dist/utils/__tests__/shell.test.d.ts.map +1 -0
  60. package/dist/utils/__tests__/shell.test.js +89 -0
  61. package/dist/utils/__tests__/shell.test.js.map +1 -0
  62. package/dist/utils/config.d.ts.map +1 -1
  63. package/dist/utils/config.js +12 -39
  64. package/dist/utils/config.js.map +1 -1
  65. package/dist/utils/deployment-selector.d.ts +50 -0
  66. package/dist/utils/deployment-selector.d.ts.map +1 -0
  67. package/dist/utils/deployment-selector.js +129 -0
  68. package/dist/utils/deployment-selector.js.map +1 -0
  69. package/dist/utils/init/convex-setup.d.ts +30 -0
  70. package/dist/utils/init/convex-setup.d.ts.map +1 -0
  71. package/dist/utils/init/convex-setup.js +225 -0
  72. package/dist/utils/init/convex-setup.js.map +1 -0
  73. package/dist/utils/init/env-generator.d.ts +32 -0
  74. package/dist/utils/init/env-generator.d.ts.map +1 -0
  75. package/dist/utils/init/env-generator.js +210 -0
  76. package/dist/utils/init/env-generator.js.map +1 -0
  77. package/dist/utils/init/file-operations.d.ts +22 -0
  78. package/dist/utils/init/file-operations.d.ts.map +1 -0
  79. package/dist/utils/init/file-operations.js +211 -0
  80. package/dist/utils/init/file-operations.js.map +1 -0
  81. package/dist/utils/init/graph-setup.d.ts +35 -0
  82. package/dist/utils/init/graph-setup.d.ts.map +1 -0
  83. package/dist/utils/init/graph-setup.js +413 -0
  84. package/dist/utils/init/graph-setup.js.map +1 -0
  85. package/dist/utils/init/index.d.ts +11 -0
  86. package/dist/utils/init/index.d.ts.map +1 -0
  87. package/dist/utils/init/index.js +11 -0
  88. package/dist/utils/init/index.js.map +1 -0
  89. package/dist/utils/init/types.d.ts +73 -0
  90. package/dist/utils/init/types.d.ts.map +1 -0
  91. package/dist/utils/init/types.js +5 -0
  92. package/dist/utils/init/types.js.map +1 -0
  93. package/dist/utils/shell.d.ts +60 -0
  94. package/dist/utils/shell.d.ts.map +1 -0
  95. package/dist/utils/shell.js +188 -0
  96. package/dist/utils/shell.js.map +1 -0
  97. package/package.json +25 -20
  98. package/templates/basic/README.md +105 -0
  99. package/templates/basic/dev-runner.mjs +215 -0
  100. package/templates/basic/package-lock.json +1263 -0
  101. package/templates/basic/package.json +22 -0
  102. package/templates/basic/src/index.ts +85 -0
  103. package/templates/basic/tsconfig.json +17 -0
@@ -0,0 +1,895 @@
1
+ /**
2
+ * Init Command
3
+ *
4
+ * Interactive wizard for setting up a new Cortex Memory project.
5
+ * Replaces `npx create-cortex-memories` with a more integrated CLI experience.
6
+ */
7
+ import prompts from "prompts";
8
+ import path from "path";
9
+ import fs from "fs-extra";
10
+ import pc from "picocolors";
11
+ import ora from "ora";
12
+ import { spawn } from "child_process";
13
+ import { loadConfig, saveUserConfig } from "../utils/config.js";
14
+ import { isValidProjectName, isDirectoryEmpty, fetchLatestSDKMetadata, execCommand, commandExists, } from "../utils/shell.js";
15
+ import { setupNewConvex, setupExistingConvex, deployToConvex, } from "../utils/init/convex-setup.js";
16
+ import { getGraphConfig, setupGraphFiles, addGraphDependencies, createGraphExample, startGraphContainers, stopGraphContainers, } from "../utils/init/graph-setup.js";
17
+ import { copyTemplate, deployCortexBackend, createConvexJson, ensureGitignore, } from "../utils/init/file-operations.js";
18
+ import { createEnvFile, appendGraphEnvVars } from "../utils/init/env-generator.js";
19
+ /**
20
+ * Register start and stop commands (lifecycle management)
21
+ */
22
+ export function registerLifecycleCommands(program, _config) {
23
+ // Quick start command
24
+ program
25
+ .command("start")
26
+ .description("Start development services (all enabled deployments)")
27
+ .option("-d, --deployment <name>", "Start a specific deployment only")
28
+ .option("-l, --local", "Use Convex local beta mode (starts a new local backend)", false)
29
+ .option("-f, --foreground", "Run in foreground (only works with single deployment)", false)
30
+ .option("--convex-only", "Only start Convex servers", false)
31
+ .option("--graph-only", "Only start graph databases", false)
32
+ .action(async (options) => {
33
+ const config = await loadConfig();
34
+ const deploymentsToStart = [];
35
+ if (options.deployment) {
36
+ // Start specific deployment
37
+ const deployment = config.deployments[options.deployment];
38
+ if (!deployment) {
39
+ console.error(pc.red(`\n Deployment "${options.deployment}" not found`));
40
+ const names = Object.keys(config.deployments);
41
+ if (names.length > 0) {
42
+ console.log(pc.dim(` Available: ${names.join(", ")}`));
43
+ }
44
+ console.log(pc.dim(" Run 'cortex config list' to see all deployments"));
45
+ process.exit(1);
46
+ }
47
+ const projectPath = deployment.projectPath || process.cwd();
48
+ if (deployment.projectPath && !fs.existsSync(projectPath)) {
49
+ console.error(pc.red(`\n Project path not found: ${projectPath}`));
50
+ console.log(pc.dim(" Run 'cortex config set-path <deployment> <path>' to update"));
51
+ process.exit(1);
52
+ }
53
+ deploymentsToStart.push({
54
+ name: options.deployment,
55
+ url: deployment.url,
56
+ key: deployment.key,
57
+ projectPath,
58
+ // Only use --local flag if explicitly requested
59
+ // Self-hosted backends (localhost URLs) should NOT use --local
60
+ // as that starts a new local server instead of connecting to existing one
61
+ isLocal: options.local,
62
+ });
63
+ }
64
+ else {
65
+ // Start all enabled deployments
66
+ for (const [name, deployment] of Object.entries(config.deployments)) {
67
+ const isDefault = name === config.default;
68
+ const isEnabled = deployment.enabled === true || (deployment.enabled === undefined && isDefault);
69
+ if (!isEnabled)
70
+ continue;
71
+ if (!deployment.projectPath) {
72
+ console.log(pc.yellow(` Skipping "${name}" - no projectPath configured`));
73
+ console.log(pc.dim(` Run 'cortex config set-path ${name} <path>' to configure\n`));
74
+ continue;
75
+ }
76
+ if (!fs.existsSync(deployment.projectPath)) {
77
+ console.log(pc.yellow(` Skipping "${name}" - projectPath not found: ${deployment.projectPath}`));
78
+ continue;
79
+ }
80
+ deploymentsToStart.push({
81
+ name,
82
+ url: deployment.url,
83
+ key: deployment.key,
84
+ projectPath: deployment.projectPath,
85
+ // Only use --local flag if explicitly requested
86
+ // Self-hosted backends (localhost URLs) should NOT use --local
87
+ isLocal: options.local,
88
+ });
89
+ }
90
+ }
91
+ if (deploymentsToStart.length === 0) {
92
+ console.log(pc.yellow("\n No deployments to start"));
93
+ console.log(pc.dim(" Run 'cortex config enable <name>' to enable a deployment"));
94
+ console.log(pc.dim(" Or use 'cortex start -d <name>' to start a specific one"));
95
+ process.exit(0);
96
+ }
97
+ // Foreground mode only works with single deployment
98
+ if (options.foreground && deploymentsToStart.length > 1) {
99
+ console.error(pc.red("\n Foreground mode only works with a single deployment"));
100
+ console.log(pc.dim(" Use 'cortex start -d <name> -f' for foreground mode"));
101
+ process.exit(1);
102
+ }
103
+ console.log(pc.cyan(`\n Starting ${deploymentsToStart.length} deployment(s)...\n`));
104
+ // Start each deployment
105
+ for (const dep of deploymentsToStart) {
106
+ console.log(pc.bold(` ${dep.name}`));
107
+ console.log(pc.dim(` Project: ${dep.projectPath}`));
108
+ console.log(pc.dim(` URL: ${dep.url}\n`));
109
+ // Start graph database if configured and not convex-only
110
+ if (!options.convexOnly) {
111
+ const dockerComposePath = path.join(dep.projectPath, "docker-compose.graph.yml");
112
+ if (fs.existsSync(dockerComposePath)) {
113
+ const composeContent = await fs.readFile(dockerComposePath, "utf-8");
114
+ const graphType = composeContent.includes("memgraph")
115
+ ? "memgraph"
116
+ : "neo4j";
117
+ await startGraphContainers(dep.projectPath, graphType);
118
+ }
119
+ }
120
+ // Start Convex if not graph-only
121
+ if (!options.graphOnly) {
122
+ const hasConvex = await commandExists("convex");
123
+ const env = { ...process.env };
124
+ env.CONVEX_URL = dep.url;
125
+ if (dep.key)
126
+ env.CONVEX_DEPLOY_KEY = dep.key;
127
+ // For cloud deployments with a deploy key, run `convex deploy` first
128
+ // This ensures functions are deployed to production before starting dev mode
129
+ if (dep.key && !dep.isLocal) {
130
+ const deploySpinner = ora("Deploying functions to production...").start();
131
+ try {
132
+ const deployCmd = hasConvex ? "convex" : "npx";
133
+ const deployArgs = hasConvex
134
+ ? ["deploy", "--cmd", "echo deployed"]
135
+ : ["convex", "deploy", "--cmd", "echo deployed"];
136
+ await new Promise((resolve, reject) => {
137
+ const deployChild = spawn(deployCmd, deployArgs, {
138
+ cwd: dep.projectPath,
139
+ stdio: "pipe",
140
+ env,
141
+ });
142
+ let stderr = "";
143
+ deployChild.stderr?.on("data", (data) => { stderr += data.toString(); });
144
+ deployChild.on("close", (code) => {
145
+ if (code === 0) {
146
+ resolve();
147
+ }
148
+ else {
149
+ reject(new Error(`Deploy failed with code ${code}: ${stderr}`));
150
+ }
151
+ });
152
+ deployChild.on("error", reject);
153
+ });
154
+ deploySpinner.succeed("Functions deployed to production");
155
+ }
156
+ catch (error) {
157
+ deploySpinner.fail("Failed to deploy functions");
158
+ console.error(pc.dim(` ${error}`));
159
+ console.log(pc.yellow(" Continuing with dev mode anyway..."));
160
+ }
161
+ }
162
+ if (options.foreground) {
163
+ // Foreground mode - blocking (single deployment only)
164
+ console.log(pc.cyan(" Starting Convex development server...\n"));
165
+ console.log(pc.dim(" Press Ctrl+C to stop\n"));
166
+ const command = hasConvex ? "convex" : "npx";
167
+ const args = hasConvex ? ["dev"] : ["convex", "dev"];
168
+ if (dep.isLocal)
169
+ args.push("--local");
170
+ const child = spawn(command, args, {
171
+ cwd: dep.projectPath,
172
+ stdio: "inherit",
173
+ env,
174
+ });
175
+ await new Promise((resolve) => {
176
+ child.on("close", () => resolve());
177
+ });
178
+ }
179
+ else {
180
+ // Background mode
181
+ await startConvexInBackground(dep.projectPath, dep.isLocal, dep.url, dep.key);
182
+ }
183
+ }
184
+ }
185
+ // Show summary
186
+ if (!options.foreground) {
187
+ console.log(pc.green("\n ✓ All deployments started\n"));
188
+ console.log(pc.dim(" Use 'cortex stop' to stop all services"));
189
+ console.log(pc.dim(" Use 'cortex config list' to see deployment status"));
190
+ }
191
+ });
192
+ // Stop command
193
+ program
194
+ .command("stop")
195
+ .description("Stop background services (Convex and graph database)")
196
+ .option("-d, --deployment <name>", "Stop specific deployment only")
197
+ .option("--convex-only", "Only stop Convex server", false)
198
+ .option("--graph-only", "Only stop graph database", false)
199
+ .action(async (options) => {
200
+ let stoppedSomething = false;
201
+ let stoppedCount = 0;
202
+ // Determine which deployments to stop
203
+ const deploymentsToStop = [];
204
+ if (options.deployment) {
205
+ // Stop specific deployment
206
+ const deployment = _config.deployments?.[options.deployment];
207
+ if (!deployment) {
208
+ console.error(pc.red(`\n Error: Deployment "${options.deployment}" not found`));
209
+ console.log(pc.dim(" Run 'cortex config list' to see available deployments\n"));
210
+ process.exit(1);
211
+ }
212
+ if (!deployment.projectPath) {
213
+ console.error(pc.red(`\n Error: Deployment "${options.deployment}" has no project path`));
214
+ console.log(pc.dim(" This deployment may be remote-only\n"));
215
+ process.exit(1);
216
+ }
217
+ deploymentsToStop.push({ name: options.deployment, projectPath: deployment.projectPath });
218
+ }
219
+ else {
220
+ // Stop all deployments that have running processes
221
+ const deploymentEntries = Object.entries(_config.deployments || {});
222
+ if (deploymentEntries.length === 0) {
223
+ // Fallback to current directory
224
+ const cwd = process.cwd();
225
+ deploymentsToStop.push({ name: "current directory", projectPath: cwd });
226
+ }
227
+ else {
228
+ for (const [name, deployment] of deploymentEntries) {
229
+ if (deployment.projectPath && fs.existsSync(deployment.projectPath)) {
230
+ // Check if anything is running for this deployment
231
+ const pidFile = path.join(deployment.projectPath, ".convex-dev.pid");
232
+ const dockerCompose = path.join(deployment.projectPath, "docker-compose.graph.yml");
233
+ const hasPidFile = fs.existsSync(pidFile);
234
+ const hasDockerCompose = fs.existsSync(dockerCompose);
235
+ if (hasPidFile || hasDockerCompose) {
236
+ deploymentsToStop.push({ name, projectPath: deployment.projectPath });
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+ if (deploymentsToStop.length === 0) {
243
+ console.log(pc.yellow("\n No deployments with running services found\n"));
244
+ return;
245
+ }
246
+ console.log(pc.cyan(`\n Stopping ${deploymentsToStop.length} deployment(s)...\n`));
247
+ for (const { name, projectPath } of deploymentsToStop) {
248
+ console.log(pc.bold(` ${name}`));
249
+ console.log(pc.dim(` ${projectPath}`));
250
+ let deploymentStopped = false;
251
+ // Stop Convex if not graph-only
252
+ if (!options.graphOnly) {
253
+ const pidFile = path.join(projectPath, ".convex-dev.pid");
254
+ try {
255
+ const pid = await fs.readFile(pidFile, "utf-8");
256
+ const pidNum = parseInt(pid.trim());
257
+ try {
258
+ process.kill(pidNum, "SIGTERM");
259
+ console.log(pc.green(` ✓ Convex stopped (PID: ${pidNum})`));
260
+ stoppedSomething = true;
261
+ deploymentStopped = true;
262
+ }
263
+ catch (e) {
264
+ const err = e;
265
+ if (err.code === "ESRCH") {
266
+ console.log(pc.yellow(" Convex was already stopped"));
267
+ }
268
+ else {
269
+ throw e;
270
+ }
271
+ }
272
+ // Clean up pid file
273
+ await fs.remove(pidFile);
274
+ }
275
+ catch (e) {
276
+ const err = e;
277
+ if (err.code !== "ENOENT") {
278
+ console.error(pc.red(" Error stopping Convex:"), e);
279
+ }
280
+ else if (!options.graphOnly) {
281
+ console.log(pc.dim(" No Convex process running"));
282
+ }
283
+ }
284
+ }
285
+ // Stop graph containers if not convex-only
286
+ if (!options.convexOnly) {
287
+ const dockerComposePath = path.join(projectPath, "docker-compose.graph.yml");
288
+ if (fs.existsSync(dockerComposePath)) {
289
+ const stopped = await stopGraphContainers(projectPath);
290
+ if (stopped) {
291
+ console.log(pc.green(" ✓ Graph database stopped"));
292
+ stoppedSomething = true;
293
+ deploymentStopped = true;
294
+ }
295
+ else {
296
+ console.log(pc.dim(" No graph container running"));
297
+ }
298
+ }
299
+ else if (!options.convexOnly) {
300
+ console.log(pc.dim(" No graph database configured"));
301
+ }
302
+ }
303
+ if (deploymentStopped) {
304
+ stoppedCount++;
305
+ }
306
+ console.log();
307
+ }
308
+ if (stoppedSomething) {
309
+ console.log(pc.green(` ✓ Stopped ${stoppedCount} deployment(s)\n`));
310
+ }
311
+ else {
312
+ console.log(pc.yellow(" No services were running\n"));
313
+ }
314
+ });
315
+ }
316
+ /**
317
+ * Register init command (project initialization)
318
+ */
319
+ export function registerInitCommand(program, _config) {
320
+ // Init command
321
+ program
322
+ .command("init [directory]")
323
+ .description("Initialize a new Cortex Memory project")
324
+ .option("--local", "Quick setup with local Convex only", false)
325
+ .option("--cloud", "Quick setup with cloud Convex only", false)
326
+ .option("--skip-graph", "Skip graph database setup", false)
327
+ .option("-t, --template <name>", "Template to use (default: basic)", "basic")
328
+ .option("-y, --yes", "Skip confirmation prompts", false)
329
+ .option("--start", "Start Convex dev server after setup", false)
330
+ .action(async (targetDir, options) => {
331
+ try {
332
+ await runInitWizard(targetDir, options);
333
+ }
334
+ catch (error) {
335
+ if (error instanceof Error) {
336
+ if (error.message === "Setup cancelled") {
337
+ process.exit(0);
338
+ }
339
+ console.error(pc.red("\n Error:"), error.message);
340
+ }
341
+ else {
342
+ console.error(pc.red("\n An unexpected error occurred:"), error);
343
+ }
344
+ process.exit(1);
345
+ }
346
+ });
347
+ }
348
+ /**
349
+ * Run the interactive init wizard
350
+ */
351
+ export async function runInitWizard(targetDir, options = {}) {
352
+ console.log(pc.bold(pc.cyan("\n Cortex Memory Project Setup\n")));
353
+ console.log(pc.dim(" Setting up AI agent with persistent memory\n"));
354
+ // Step 1: Project name and location
355
+ const projectInfo = await getProjectInfo(targetDir);
356
+ // Step 2: Installation type
357
+ const installationType = await getInstallationType(projectInfo.projectPath);
358
+ // Step 3: Convex setup
359
+ const convexConfig = await getConvexSetup(options);
360
+ // Step 4: Graph database (optional)
361
+ let graphConfig = null;
362
+ if (!options.skipGraph) {
363
+ graphConfig = await getGraphConfig();
364
+ }
365
+ // Step 5: CLI scripts option
366
+ const installCLI = await getCliInstallOption();
367
+ // Build wizard configuration
368
+ const config = {
369
+ projectName: projectInfo.projectName,
370
+ projectPath: projectInfo.projectPath,
371
+ installationType,
372
+ convexSetupType: convexConfig.type,
373
+ convexUrl: convexConfig.config.convexUrl,
374
+ deployKey: convexConfig.config.deployKey,
375
+ graphEnabled: graphConfig !== null,
376
+ graphType: graphConfig?.type || "skip",
377
+ graphUri: graphConfig?.uri,
378
+ graphUsername: graphConfig?.username,
379
+ graphPassword: graphConfig?.password,
380
+ installCLI,
381
+ };
382
+ // Show confirmation
383
+ if (!options.yes) {
384
+ await showConfirmation(config);
385
+ }
386
+ // Execute setup
387
+ await executeSetup(config);
388
+ // Ask to start Convex if not already specified
389
+ let shouldStart = options.start;
390
+ if (!shouldStart && !options.yes) {
391
+ const response = await prompts({
392
+ type: "confirm",
393
+ name: "startNow",
394
+ message: "Start Convex development server now?",
395
+ initial: true,
396
+ });
397
+ shouldStart = response.startNow;
398
+ }
399
+ // Start Convex if requested
400
+ if (shouldStart) {
401
+ const isLocal = config.convexSetupType === "local";
402
+ await startConvexInBackground(config.projectPath, isLocal);
403
+ // Show running status dashboard
404
+ console.log();
405
+ await showRunningStatus(config.projectPath, isLocal);
406
+ }
407
+ }
408
+ /**
409
+ * Get project name and location
410
+ */
411
+ async function getProjectInfo(targetDir) {
412
+ if (targetDir) {
413
+ const projectPath = path.resolve(targetDir);
414
+ const projectName = targetDir === "."
415
+ ? path.basename(process.cwd())
416
+ : path.basename(projectPath);
417
+ return { projectName, projectPath };
418
+ }
419
+ const response = await prompts({
420
+ type: "text",
421
+ name: "projectName",
422
+ message: "Project name:",
423
+ initial: "my-cortex-agent",
424
+ validate: (value) => {
425
+ if (!value)
426
+ return "Project name is required";
427
+ if (!isValidProjectName(value)) {
428
+ return "Project name must contain only lowercase letters, numbers, hyphens, and underscores";
429
+ }
430
+ return true;
431
+ },
432
+ });
433
+ if (!response.projectName) {
434
+ throw new Error("Project name is required");
435
+ }
436
+ const projectPath = path.resolve(response.projectName);
437
+ return {
438
+ projectName: response.projectName,
439
+ projectPath,
440
+ };
441
+ }
442
+ /**
443
+ * Get installation type
444
+ */
445
+ async function getInstallationType(projectPath) {
446
+ const exists = fs.existsSync(projectPath);
447
+ const isEmpty = isDirectoryEmpty(projectPath);
448
+ if (exists && !isEmpty) {
449
+ const response = await prompts({
450
+ type: "confirm",
451
+ name: "addToExisting",
452
+ message: `Directory ${path.basename(projectPath)} already exists. Add Cortex to existing project?`,
453
+ initial: true,
454
+ });
455
+ if (!response.addToExisting) {
456
+ throw new Error("Setup cancelled");
457
+ }
458
+ return "existing";
459
+ }
460
+ return "new";
461
+ }
462
+ /**
463
+ * Get Convex setup configuration
464
+ */
465
+ async function getConvexSetup(options) {
466
+ // Quick options
467
+ if (options.local) {
468
+ return {
469
+ type: "local",
470
+ config: { convexUrl: "http://127.0.0.1:3210" },
471
+ };
472
+ }
473
+ if (options.cloud) {
474
+ return {
475
+ type: "new",
476
+ config: { convexUrl: "" },
477
+ };
478
+ }
479
+ console.log(pc.cyan("\n Convex Database Setup"));
480
+ console.log(pc.dim(" Cortex uses Convex as its backend database\n"));
481
+ const response = await prompts({
482
+ type: "select",
483
+ name: "setupType",
484
+ message: "How would you like to set up Convex?",
485
+ choices: [
486
+ {
487
+ title: "Create new Convex project",
488
+ description: "Full features including vector search",
489
+ value: "new",
490
+ },
491
+ {
492
+ title: "Use existing Convex deployment",
493
+ description: "Connect to your existing deployment",
494
+ value: "existing",
495
+ },
496
+ ],
497
+ initial: 0,
498
+ });
499
+ if (!response.setupType) {
500
+ throw new Error("Convex setup is required");
501
+ }
502
+ return {
503
+ type: response.setupType,
504
+ config: {
505
+ convexUrl: "",
506
+ deployKey: undefined,
507
+ },
508
+ };
509
+ }
510
+ /**
511
+ * Get CLI installation option
512
+ */
513
+ async function getCliInstallOption() {
514
+ console.log(pc.cyan("\n CLI Scripts (Optional)"));
515
+ console.log(pc.dim(" Add npm scripts for common Cortex commands\n"));
516
+ const response = await prompts({
517
+ type: "confirm",
518
+ name: "installCLI",
519
+ message: "Add Cortex CLI scripts to package.json?",
520
+ initial: true,
521
+ });
522
+ return response.installCLI ?? false;
523
+ }
524
+ /**
525
+ * Show confirmation screen
526
+ */
527
+ async function showConfirmation(config) {
528
+ console.log(pc.cyan("\n Configuration Summary"));
529
+ console.log(pc.dim(" " + "─".repeat(46)));
530
+ console.log(pc.bold(" Project:"), config.projectName);
531
+ console.log(pc.bold(" Location:"), config.projectPath);
532
+ console.log(pc.bold(" Type:"), config.installationType === "new" ? "New project" : "Add to existing");
533
+ console.log(pc.bold(" Convex:"), config.convexSetupType === "new" ? "New Convex project" : "Existing deployment");
534
+ console.log(pc.bold(" Graph DB:"), config.graphEnabled ? config.graphType : "Disabled");
535
+ console.log(pc.bold(" CLI Scripts:"), config.installCLI ? "Yes" : "No");
536
+ console.log(pc.dim(" " + "─".repeat(46)));
537
+ const response = await prompts({
538
+ type: "confirm",
539
+ name: "confirm",
540
+ message: "Proceed with setup?",
541
+ initial: true,
542
+ });
543
+ if (!response.confirm) {
544
+ console.log(pc.yellow("\n Setup cancelled"));
545
+ process.exit(0);
546
+ }
547
+ }
548
+ /**
549
+ * Execute the setup
550
+ */
551
+ async function executeSetup(config) {
552
+ console.log(pc.cyan("\n Setting up Cortex...\n"));
553
+ try {
554
+ // Create project directory
555
+ await fs.ensureDir(config.projectPath);
556
+ // Fetch SDK metadata to get correct Convex version
557
+ const metadataSpinner = ora("Fetching SDK metadata...").start();
558
+ const sdkMetadata = await fetchLatestSDKMetadata();
559
+ metadataSpinner.succeed(`SDK v${sdkMetadata.sdkVersion} (Convex ${sdkMetadata.convexVersion})`);
560
+ // Copy template files (check if package.json exists)
561
+ const needsTemplate = !fs.existsSync(path.join(config.projectPath, "package.json"));
562
+ if (needsTemplate || config.installationType === "new") {
563
+ const spinner = ora("Creating project files...").start();
564
+ await copyTemplate("basic", config.projectPath, config.projectName, sdkMetadata.convexVersion);
565
+ spinner.succeed("Project files created");
566
+ }
567
+ else {
568
+ console.log(pc.dim(" Using existing project files"));
569
+ }
570
+ // Create .gitignore first (before any generated files)
571
+ await ensureGitignore(config.projectPath);
572
+ // Install dependencies FIRST - required for convex dev to work
573
+ const installSpinner = ora("Installing dependencies...").start();
574
+ const result = await execCommand("npm", ["install"], {
575
+ cwd: config.projectPath,
576
+ quiet: true,
577
+ });
578
+ if (result.code !== 0) {
579
+ installSpinner.fail("Failed to install dependencies");
580
+ console.error(pc.red(result.stderr));
581
+ throw new Error("npm install failed");
582
+ }
583
+ installSpinner.succeed("Dependencies installed");
584
+ // Verify convex was installed (required for convex dev)
585
+ const convexCheck = fs.existsSync(path.join(config.projectPath, "node_modules", "convex"));
586
+ if (!convexCheck) {
587
+ console.warn(pc.yellow(" Warning: convex package not found in node_modules"));
588
+ console.log(pc.dim(" This may cause Convex setup to fail"));
589
+ }
590
+ // Copy Cortex backend functions FIRST (before convex dev)
591
+ // This way we deploy everything in ONE step
592
+ const backendSpinner = ora("Setting up Cortex backend functions...").start();
593
+ await deployCortexBackend(config.projectPath);
594
+ await createConvexJson(config.projectPath);
595
+ backendSpinner.succeed("Cortex backend files ready");
596
+ // Setup and deploy Convex in ONE step
597
+ let convexConfig;
598
+ if (config.convexSetupType === "new") {
599
+ // For new projects: prompt for project creation and deploy everything
600
+ convexConfig = await setupNewConvex(config.projectPath);
601
+ }
602
+ else {
603
+ // For existing projects: get URL/key, then deploy
604
+ convexConfig = await setupExistingConvex();
605
+ await deployToConvex(config.projectPath, convexConfig, false);
606
+ }
607
+ // Update config with actual Convex details
608
+ config.convexUrl = convexConfig.convexUrl;
609
+ config.deployKey = convexConfig.deployKey;
610
+ // Create .env.local (may already exist from convex dev, but ensure our values)
611
+ await createEnvFile(config.projectPath, config);
612
+ // Read actual Convex URL from .env.local (convex dev may have created/updated it)
613
+ const envLocalPath = path.join(config.projectPath, ".env.local");
614
+ if (fs.existsSync(envLocalPath)) {
615
+ try {
616
+ const envContent = await fs.readFile(envLocalPath, "utf-8");
617
+ const urlMatch = envContent.match(/CONVEX_URL=(.+)/);
618
+ const keyMatch = envContent.match(/CONVEX_DEPLOY_KEY=(.+)/);
619
+ if (urlMatch) {
620
+ config.convexUrl = urlMatch[1].trim();
621
+ }
622
+ if (keyMatch && !config.deployKey) {
623
+ config.deployKey = keyMatch[1].trim();
624
+ }
625
+ }
626
+ catch {
627
+ // Ignore read errors
628
+ }
629
+ }
630
+ // Setup graph database if enabled
631
+ if (config.graphEnabled && config.graphType !== "skip") {
632
+ const graphSpinner = ora("Configuring graph database...").start();
633
+ await setupGraphFiles(config.projectPath, {
634
+ type: config.graphType,
635
+ uri: config.graphUri,
636
+ username: config.graphUsername,
637
+ password: config.graphPassword,
638
+ });
639
+ // Add graph env vars to .env.local (after Convex may have modified it)
640
+ await appendGraphEnvVars(config.projectPath, {
641
+ graphUri: config.graphUri,
642
+ graphUsername: config.graphUsername,
643
+ graphPassword: config.graphPassword,
644
+ });
645
+ await addGraphDependencies(config.projectPath);
646
+ await createGraphExample(config.projectPath);
647
+ graphSpinner.succeed("Graph database configured");
648
+ // Start graph containers if local deployment
649
+ const isLocalGraph = config.graphUri?.includes("localhost") ||
650
+ config.graphUri?.includes("127.0.0.1");
651
+ if (isLocalGraph) {
652
+ console.log();
653
+ await startGraphContainers(config.projectPath, config.graphType);
654
+ }
655
+ }
656
+ // Add CLI scripts if requested
657
+ if (config.installCLI) {
658
+ await addCLIScripts(config.projectPath);
659
+ }
660
+ // Save deployment to user config (~/.cortexrc)
661
+ await saveDeploymentToConfig(config);
662
+ // Success!
663
+ showSuccessMessage(config);
664
+ }
665
+ catch (error) {
666
+ console.error(pc.red("\n Setup failed:"), error);
667
+ throw error;
668
+ }
669
+ }
670
+ /**
671
+ * Save deployment configuration to ~/.cortexrc
672
+ */
673
+ async function saveDeploymentToConfig(config) {
674
+ // Skip if no URL was configured
675
+ if (!config.convexUrl) {
676
+ return;
677
+ }
678
+ try {
679
+ const userConfig = await loadConfig();
680
+ // Always use project name as deployment name (sanitized)
681
+ // This keeps deployment names consistent regardless of URL type
682
+ const deploymentName = config.projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
683
+ // Add the deployment
684
+ userConfig.deployments[deploymentName] = {
685
+ url: config.convexUrl,
686
+ key: config.deployKey,
687
+ projectPath: config.projectPath,
688
+ };
689
+ // Set as default if no default exists
690
+ if (!userConfig.default) {
691
+ userConfig.default = deploymentName;
692
+ }
693
+ await saveUserConfig(userConfig);
694
+ console.log(pc.dim(` Saved deployment "${deploymentName}" to config`));
695
+ }
696
+ catch {
697
+ // Non-critical error - warn but don't fail
698
+ console.warn(pc.yellow(" Warning: Could not save deployment to config"));
699
+ console.log(pc.dim(" Run 'cortex config add-deployment' to add manually"));
700
+ }
701
+ }
702
+ /**
703
+ * Add CLI scripts to package.json
704
+ */
705
+ async function addCLIScripts(projectPath) {
706
+ const packageJsonPath = path.join(projectPath, "package.json");
707
+ try {
708
+ const packageJson = await fs.readJson(packageJsonPath);
709
+ // Add CLI scripts
710
+ packageJson.scripts = packageJson.scripts || {};
711
+ packageJson.scripts["cortex"] = "cortex";
712
+ packageJson.scripts["cortex:setup"] = "cortex setup";
713
+ packageJson.scripts["cortex:stats"] = "cortex db stats";
714
+ packageJson.scripts["cortex:spaces"] = "cortex spaces list";
715
+ packageJson.scripts["cortex:status"] = "cortex status";
716
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
717
+ console.log(pc.dim(" Added CLI scripts to package.json"));
718
+ }
719
+ catch {
720
+ // Non-critical, skip silently
721
+ }
722
+ }
723
+ /**
724
+ * Show success message
725
+ */
726
+ function showSuccessMessage(config) {
727
+ console.log(pc.bold(pc.green("\n Cortex Memory successfully initialized!\n")));
728
+ console.log(pc.bold(" Project:"), config.projectName);
729
+ console.log(pc.bold(" Database:"), config.convexSetupType === "local"
730
+ ? "Local Convex (development)"
731
+ : `Convex Cloud (${config.convexUrl})`);
732
+ if (config.graphEnabled && config.graphType !== "skip") {
733
+ console.log(pc.bold(" Graph:"), config.graphType, "(configured)");
734
+ }
735
+ console.log(pc.green("\n Setup complete!\n"));
736
+ console.log(pc.bold(" Next steps:\n"));
737
+ if (config.installationType === "new") {
738
+ console.log(pc.cyan(` cd ${config.projectName}`));
739
+ }
740
+ if (config.convexSetupType === "local") {
741
+ console.log(pc.cyan(" npm run dev") + pc.dim(" # Start Convex in watch mode"));
742
+ console.log(pc.dim(" (Then in another terminal)"));
743
+ console.log(pc.cyan(" npm start") + pc.dim(" # Run your AI agent"));
744
+ console.log(pc.dim("\n Dashboard: http://127.0.0.1:3210"));
745
+ }
746
+ else {
747
+ console.log(pc.cyan(" npm start") + pc.dim(" # Run your AI agent"));
748
+ console.log(pc.dim(`\n Dashboard: ${config.convexUrl?.replace("/api", "")}`));
749
+ }
750
+ // CLI commands
751
+ console.log(pc.bold("\n CLI Commands:\n"));
752
+ console.log(pc.cyan(" cortex status") +
753
+ pc.dim(" # View setup status dashboard"));
754
+ console.log(pc.cyan(" cortex db stats") +
755
+ pc.dim(" # View database statistics"));
756
+ console.log(pc.cyan(" cortex spaces list") + pc.dim(" # List memory spaces"));
757
+ console.log(pc.cyan(" cortex --help") + pc.dim(" # See all CLI commands"));
758
+ console.log(pc.bold("\n Learn more:\n"));
759
+ console.log(pc.dim(" Documentation: https://github.com/SaintNick1214/Project-Cortex/tree/main/Documentation"));
760
+ console.log(pc.dim(" Examples: https://github.com/SaintNick1214/Project-Cortex/tree/main/Examples"));
761
+ console.log(pc.bold(pc.cyan("\n Happy building with Cortex!\n")));
762
+ }
763
+ /**
764
+ * Start Convex development server in background
765
+ * Note: Deploy to production is handled by the caller before this function
766
+ */
767
+ async function startConvexInBackground(projectPath, isLocal, deploymentUrl, deploymentKey) {
768
+ const hasConvex = await commandExists("convex");
769
+ const command = hasConvex ? "convex" : "npx";
770
+ // Set up environment with deployment-specific URL/key if provided
771
+ const env = { ...process.env };
772
+ if (deploymentUrl)
773
+ env.CONVEX_URL = deploymentUrl;
774
+ if (deploymentKey)
775
+ env.CONVEX_DEPLOY_KEY = deploymentKey;
776
+ // Start convex dev in background for watch mode
777
+ const devSpinner = ora("Starting Convex development server...").start();
778
+ const args = hasConvex ? ["dev"] : ["convex", "dev"];
779
+ if (isLocal) {
780
+ args.push("--local");
781
+ }
782
+ // Create log file for the background process
783
+ const logFile = path.join(projectPath, ".convex-dev.log");
784
+ // Use fs.openSync to get a file descriptor (required for detached process stdio)
785
+ const logFd = fs.openSync(logFile, "a");
786
+ // Spawn detached process
787
+ const child = spawn(command, args, {
788
+ cwd: projectPath,
789
+ detached: true,
790
+ stdio: ["ignore", logFd, logFd],
791
+ env,
792
+ });
793
+ // Close the file descriptor in parent process (child keeps it open)
794
+ fs.closeSync(logFd);
795
+ // Unref so parent can exit independently
796
+ child.unref();
797
+ // Save PID for later management
798
+ const pidFile = path.join(projectPath, ".convex-dev.pid");
799
+ await fs.writeFile(pidFile, String(child.pid));
800
+ // Wait a moment for startup
801
+ await new Promise((resolve) => setTimeout(resolve, 2000));
802
+ devSpinner.succeed("Convex development server started in background");
803
+ console.log(pc.dim(` PID: ${child.pid}`));
804
+ console.log(pc.dim(` Log: ${path.basename(logFile)}`));
805
+ }
806
+ /**
807
+ * Show running services status dashboard
808
+ */
809
+ async function showRunningStatus(projectPath, isLocal) {
810
+ const width = 56;
811
+ const line = "═".repeat(width);
812
+ const thinLine = "─".repeat(width);
813
+ console.log(pc.cyan("╔" + line + "╗"));
814
+ console.log(pc.cyan("║") +
815
+ pc.bold(" Cortex Development Environment").padEnd(width) +
816
+ pc.cyan("║"));
817
+ console.log(pc.cyan("╚" + line + "╝"));
818
+ console.log();
819
+ // Convex Status
820
+ console.log(pc.bold(pc.white(" Convex Backend")));
821
+ console.log(pc.dim(" " + thinLine));
822
+ const pidFile = path.join(projectPath, ".convex-dev.pid");
823
+ let convexRunning = false;
824
+ let convexPid = null;
825
+ try {
826
+ convexPid = await fs.readFile(pidFile, "utf-8");
827
+ // Check if process is running
828
+ try {
829
+ process.kill(parseInt(convexPid), 0);
830
+ convexRunning = true;
831
+ }
832
+ catch {
833
+ convexRunning = false;
834
+ }
835
+ }
836
+ catch {
837
+ convexRunning = false;
838
+ }
839
+ if (convexRunning) {
840
+ console.log(` ${pc.green("●")} Running (PID: ${convexPid})`);
841
+ if (isLocal) {
842
+ console.log(pc.dim(" Dashboard: http://127.0.0.1:3210"));
843
+ }
844
+ else {
845
+ console.log(pc.dim(` URL: ${process.env.CONVEX_URL || "configured"}`));
846
+ }
847
+ }
848
+ else {
849
+ console.log(` ${pc.yellow("○")} Not running`);
850
+ console.log(pc.dim(" Run: cortex start"));
851
+ }
852
+ console.log();
853
+ // Graph Database Status
854
+ console.log(pc.bold(pc.white(" Graph Database")));
855
+ console.log(pc.dim(" " + thinLine));
856
+ const hasGraphConfig = process.env.NEO4J_URI || fs.existsSync(path.join(projectPath, "docker-compose.graph.yml"));
857
+ if (hasGraphConfig) {
858
+ // Check if Docker container is running
859
+ try {
860
+ const result = await execCommand("docker", ["ps", "--filter", "name=cortex-neo4j", "--format", "{{.Status}}"], { quiet: true });
861
+ if (result.stdout.includes("Up")) {
862
+ console.log(` ${pc.green("●")} Neo4j running`);
863
+ console.log(pc.dim(" Browser: http://localhost:7474"));
864
+ console.log(pc.dim(" Bolt: bolt://localhost:7687"));
865
+ }
866
+ else {
867
+ console.log(` ${pc.yellow("○")} Neo4j configured but not running`);
868
+ console.log(pc.dim(" Start: docker-compose -f docker-compose.graph.yml up -d"));
869
+ }
870
+ }
871
+ catch {
872
+ console.log(` ${pc.yellow("○")} Neo4j configured but Docker not available`);
873
+ console.log(pc.dim(" Start Docker Desktop first"));
874
+ }
875
+ }
876
+ else {
877
+ console.log(` ${pc.dim("○")} Not configured`);
878
+ }
879
+ console.log();
880
+ // Quick Actions
881
+ console.log(pc.bold(pc.white(" Quick Actions")));
882
+ console.log(pc.dim(" " + thinLine));
883
+ console.log(pc.dim(" cortex status") + pc.dim(" # Full status dashboard"));
884
+ console.log(pc.dim(" cortex start -f") + pc.dim(" # Foreground mode (see logs)"));
885
+ console.log(pc.dim(" cortex stop") + pc.dim(" # Stop background services"));
886
+ console.log(pc.dim(" npm start") + pc.dim(" # Run your AI agent"));
887
+ console.log();
888
+ // Log file hint
889
+ const logFile = path.join(projectPath, ".convex-dev.log");
890
+ if (fs.existsSync(logFile)) {
891
+ console.log(pc.dim(` View logs: tail -f ${path.basename(logFile)}`));
892
+ }
893
+ console.log();
894
+ }
895
+ //# sourceMappingURL=init.js.map