@cortexmemory/cli 0.26.0 → 0.27.1

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 (71) hide show
  1. package/dist/commands/db.d.ts.map +1 -1
  2. package/dist/commands/db.js +2 -18
  3. package/dist/commands/db.js.map +1 -1
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +153 -17
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +373 -70
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/setup.d.ts.map +1 -1
  11. package/dist/commands/setup.js +102 -46
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +94 -7
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/types.d.ts +23 -0
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/utils/config.d.ts +11 -0
  20. package/dist/utils/config.d.ts.map +1 -1
  21. package/dist/utils/config.js +20 -0
  22. package/dist/utils/config.js.map +1 -1
  23. package/dist/utils/init/convex-setup.d.ts +58 -6
  24. package/dist/utils/init/convex-setup.d.ts.map +1 -1
  25. package/dist/utils/init/convex-setup.js +261 -57
  26. package/dist/utils/init/convex-setup.js.map +1 -1
  27. package/dist/utils/init/env-generator.d.ts.map +1 -1
  28. package/dist/utils/init/env-generator.js +12 -2
  29. package/dist/utils/init/env-generator.js.map +1 -1
  30. package/dist/utils/init/graph-setup.d.ts.map +1 -1
  31. package/dist/utils/init/graph-setup.js +12 -0
  32. package/dist/utils/init/graph-setup.js.map +1 -1
  33. package/dist/utils/init/quickstart-setup.d.ts +87 -0
  34. package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
  35. package/dist/utils/init/quickstart-setup.js +462 -0
  36. package/dist/utils/init/quickstart-setup.js.map +1 -0
  37. package/dist/utils/init/types.d.ts +4 -0
  38. package/dist/utils/init/types.d.ts.map +1 -1
  39. package/dist/utils/schema-sync.d.ts.map +1 -1
  40. package/dist/utils/schema-sync.js +27 -21
  41. package/dist/utils/schema-sync.js.map +1 -1
  42. package/package.json +3 -2
  43. package/templates/vercel-ai-quickstart/.env.local.example +45 -0
  44. package/templates/vercel-ai-quickstart/README.md +280 -0
  45. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +196 -0
  46. package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
  47. package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
  48. package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
  49. package/templates/vercel-ai-quickstart/app/globals.css +114 -0
  50. package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
  51. package/templates/vercel-ai-quickstart/app/page.tsx +131 -0
  52. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +237 -0
  53. package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
  54. package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
  55. package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
  56. package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
  57. package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
  58. package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
  59. package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
  60. package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
  61. package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
  62. package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
  63. package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
  64. package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
  65. package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
  66. package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
  67. package/templates/vercel-ai-quickstart/next.config.js +7 -0
  68. package/templates/vercel-ai-quickstart/package.json +41 -0
  69. package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
  70. package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
  71. package/templates/vercel-ai-quickstart/tsconfig.json +33 -0
@@ -12,7 +12,8 @@ import ora from "ora";
12
12
  import { spawn } from "child_process";
13
13
  import { loadConfig, saveUserConfig } from "../utils/config.js";
14
14
  import { isValidProjectName, isDirectoryEmpty, fetchLatestSDKMetadata, execCommand, commandExists, } from "../utils/shell.js";
15
- import { setupNewConvex, setupExistingConvex, deployToConvex, } from "../utils/init/convex-setup.js";
15
+ import { setupNewConvex, setupExistingConvex, deployToConvex, ensureConvexAuth, sanitizeProjectName, } from "../utils/init/convex-setup.js";
16
+ import { installVercelAIQuickstart, startApp, stopApp, findAppPidFiles, isAppRunning, findProcessByPort, stopProcessByPort, } from "../utils/init/quickstart-setup.js";
16
17
  import { getGraphConfig, setupGraphFiles, addGraphDependencies, createGraphExample, startGraphContainers, stopGraphContainers, } from "../utils/init/graph-setup.js";
17
18
  import { copyTemplate, deployCortexBackend, createConvexJson, ensureGitignore, } from "../utils/init/file-operations.js";
18
19
  import { createEnvFile, appendGraphEnvVars, } from "../utils/init/env-generator.js";
@@ -101,12 +102,24 @@ export function registerLifecycleCommands(program, _config) {
101
102
  console.log(pc.dim(" Use 'cortex start -d <name> -f' for foreground mode"));
102
103
  process.exit(1);
103
104
  }
104
- console.log(pc.cyan(`\n Starting ${deploymentsToStart.length} deployment(s)...\n`));
105
+ // Collect enabled apps for summary
106
+ const apps = Object.entries(config.apps || {});
107
+ const enabledApps = apps.filter(([, app]) => app.enabled);
108
+ // Show summary header
109
+ console.log();
110
+ console.log(pc.bold(" ═══════════════════════════════════════════════════════════"));
111
+ console.log(pc.bold(" Cortex Start"));
112
+ console.log(pc.bold(" ═══════════════════════════════════════════════════════════"));
113
+ console.log();
114
+ // Deployments section
115
+ console.log(pc.bold(pc.cyan(` Deployments (${deploymentsToStart.length})`)));
116
+ console.log(pc.dim(" ───────────────────────────────────────────────────────────"));
105
117
  // Start each deployment
106
118
  for (const dep of deploymentsToStart) {
107
- console.log(pc.bold(` ${dep.name}`));
108
- console.log(pc.dim(` Project: ${dep.projectPath}`));
109
- console.log(pc.dim(` URL: ${dep.url}\n`));
119
+ console.log(` ${pc.green("●")} ${pc.cyan(dep.name)}`);
120
+ console.log(pc.dim(` Project: ${dep.projectPath}`));
121
+ console.log(pc.dim(` URL: ${dep.url}`));
122
+ console.log();
110
123
  // Start graph database if configured and not convex-only
111
124
  if (!options.convexOnly) {
112
125
  const dockerComposePath = path.join(dep.projectPath, "docker-compose.graph.yml");
@@ -185,28 +198,86 @@ export function registerLifecycleCommands(program, _config) {
185
198
  }
186
199
  }
187
200
  }
201
+ // Start enabled apps (unless convex-only or graph-only)
202
+ if (!options.convexOnly && !options.graphOnly && !options.foreground) {
203
+ if (enabledApps.length > 0) {
204
+ console.log(pc.bold(pc.cyan(` Apps (${enabledApps.length})`)));
205
+ console.log(pc.dim(" ───────────────────────────────────────────────────────────"));
206
+ for (const [name, app] of enabledApps) {
207
+ console.log(` ${pc.green("●")} ${pc.cyan(name)}`);
208
+ console.log(pc.dim(` Type: ${app.type}`));
209
+ console.log(pc.dim(` Path: ${path.join(app.projectPath, app.path)}`));
210
+ console.log(pc.dim(` Port: ${app.port || 3000}`));
211
+ console.log();
212
+ await startApp(name, app);
213
+ }
214
+ }
215
+ }
188
216
  // Show summary
189
217
  if (!options.foreground) {
190
- console.log(pc.green("\n ✓ All deployments started\n"));
191
- console.log(pc.dim(" Use 'cortex stop' to stop all services"));
192
- console.log(pc.dim(" Use 'cortex config list' to see deployment status"));
218
+ const totalStarted = deploymentsToStart.length + (enabledApps.length || 0);
219
+ console.log(pc.bold(" ═══════════════════════════════════════════════════════════"));
220
+ console.log(pc.green(` ✓ Started ${totalStarted} service(s)`));
221
+ console.log(pc.bold(" ═══════════════════════════════════════════════════════════"));
222
+ console.log();
223
+ console.log(pc.dim(" Use 'cortex stop' to stop all services"));
224
+ console.log(pc.dim(" Use 'cortex config list' to see deployment status"));
225
+ console.log();
193
226
  }
194
227
  });
195
228
  // Stop command
196
229
  program
197
230
  .command("stop")
198
- .description("Stop background services (Convex and graph database)")
231
+ .description("Stop background services (Convex, graph database, and apps)")
199
232
  .option("-d, --deployment <name>", "Stop specific deployment only")
233
+ .option("-a, --app <name>", "Stop specific app only")
200
234
  .option("--convex-only", "Only stop Convex server", false)
201
235
  .option("--graph-only", "Only stop graph database", false)
236
+ .option("--apps-only", "Only stop template apps", false)
202
237
  .action(async (options) => {
238
+ // Load fresh config to get current state
239
+ const config = await loadConfig();
203
240
  let stoppedSomething = false;
204
241
  let stoppedCount = 0;
205
- // Determine which deployments to stop
242
+ let stoppedAppsCount = 0;
243
+ // If stopping specific app only
244
+ if (options.app) {
245
+ const appConfig = config.apps?.[options.app];
246
+ if (!appConfig) {
247
+ // Try to find app in current directory
248
+ const cwd = process.cwd();
249
+ const appPidFiles = await findAppPidFiles(cwd);
250
+ const matchingApp = appPidFiles.find((a) => a.name === options.app);
251
+ if (!matchingApp) {
252
+ console.error(pc.red(`\n Error: App "${options.app}" not found`));
253
+ console.log(pc.dim(" Run 'cortex config list' to see available apps\n"));
254
+ process.exit(1);
255
+ }
256
+ // Stop from current directory
257
+ const stopped = await stopApp(options.app, cwd);
258
+ if (stopped) {
259
+ console.log(pc.green(`\n ✓ App "${options.app}" stopped\n`));
260
+ }
261
+ else {
262
+ console.log(pc.yellow(`\n App "${options.app}" was not running\n`));
263
+ }
264
+ return;
265
+ }
266
+ // Stop specific configured app
267
+ const stopped = await stopApp(options.app, appConfig.projectPath);
268
+ if (stopped) {
269
+ console.log(pc.green(`\n ✓ App "${options.app}" stopped\n`));
270
+ }
271
+ else {
272
+ console.log(pc.yellow(`\n App "${options.app}" was not running\n`));
273
+ }
274
+ return;
275
+ }
276
+ // Determine which deployments/project paths to stop
206
277
  const deploymentsToStop = [];
207
278
  if (options.deployment) {
208
279
  // Stop specific deployment
209
- const deployment = _config.deployments?.[options.deployment];
280
+ const deployment = config.deployments?.[options.deployment];
210
281
  if (!deployment) {
211
282
  console.error(pc.red(`\n Error: Deployment "${options.deployment}" not found`));
212
283
  console.log(pc.dim(" Run 'cortex config list' to see available deployments\n"));
@@ -224,36 +295,52 @@ export function registerLifecycleCommands(program, _config) {
224
295
  }
225
296
  else {
226
297
  // Stop all deployments that have running processes
227
- const deploymentEntries = Object.entries(_config.deployments || {});
228
- if (deploymentEntries.length === 0) {
298
+ const deploymentEntries = Object.entries(config.deployments || {});
299
+ // Collect project paths from both deployments and apps
300
+ const projectPaths = new Map(); // path -> name
301
+ if (deploymentEntries.length === 0 && !config.apps) {
229
302
  // Fallback to current directory
230
303
  const cwd = process.cwd();
231
- deploymentsToStop.push({
232
- name: "current directory",
233
- projectPath: cwd,
234
- });
304
+ projectPaths.set(cwd, "current directory");
235
305
  }
236
306
  else {
307
+ // Add deployment project paths
237
308
  for (const [name, deployment] of deploymentEntries) {
238
309
  if (deployment.projectPath &&
239
310
  fs.existsSync(deployment.projectPath)) {
240
- // Check if anything is running for this deployment
241
- const pidFile = path.join(deployment.projectPath, ".convex-dev.pid");
242
- const dockerCompose = path.join(deployment.projectPath, "docker-compose.graph.yml");
243
- const hasPidFile = fs.existsSync(pidFile);
244
- const hasDockerCompose = fs.existsSync(dockerCompose);
245
- if (hasPidFile || hasDockerCompose) {
246
- deploymentsToStop.push({
247
- name,
248
- projectPath: deployment.projectPath,
249
- });
311
+ projectPaths.set(deployment.projectPath, name);
312
+ }
313
+ }
314
+ // Add app project paths (may overlap with deployments)
315
+ if (config.apps) {
316
+ for (const [appName, app] of Object.entries(config.apps)) {
317
+ if (app.projectPath && fs.existsSync(app.projectPath)) {
318
+ // Use app name if no deployment for this path
319
+ if (!projectPaths.has(app.projectPath)) {
320
+ projectPaths.set(app.projectPath, `app:${appName}`);
321
+ }
250
322
  }
251
323
  }
252
324
  }
253
325
  }
326
+ // Check each project path for running services
327
+ for (const [projectPath, name] of projectPaths) {
328
+ // Check if anything is running for this deployment
329
+ const pidFile = path.join(projectPath, ".convex-dev.pid");
330
+ const dockerCompose = path.join(projectPath, "docker-compose.graph.yml");
331
+ const appPidFiles = await findAppPidFiles(projectPath);
332
+ const hasPidFile = fs.existsSync(pidFile);
333
+ const hasDockerCompose = fs.existsSync(dockerCompose);
334
+ const hasApps = appPidFiles.length > 0;
335
+ if (hasPidFile || hasDockerCompose || hasApps) {
336
+ deploymentsToStop.push({ name, projectPath });
337
+ }
338
+ }
254
339
  }
255
340
  if (deploymentsToStop.length === 0) {
256
- console.log(pc.yellow("\n No deployments with running services found\n"));
341
+ console.log(pc.yellow("\n No deployments with running services found"));
342
+ console.log(pc.dim(" Note: Services started without PID files cannot be auto-detected.\n" +
343
+ " Use 'ps aux | grep convex' or 'ps aux | grep next' to find running processes.\n"));
257
344
  return;
258
345
  }
259
346
  console.log(pc.cyan(`\n Stopping ${deploymentsToStop.length} deployment(s)...\n`));
@@ -261,8 +348,9 @@ export function registerLifecycleCommands(program, _config) {
261
348
  console.log(pc.bold(` ${name}`));
262
349
  console.log(pc.dim(` ${projectPath}`));
263
350
  let deploymentStopped = false;
264
- // Stop Convex if not graph-only
265
- if (!options.graphOnly) {
351
+ let convexStopped = false;
352
+ // Stop Convex if not graph-only and not apps-only
353
+ if (!options.graphOnly && !options.appsOnly) {
266
354
  const pidFile = path.join(projectPath, ".convex-dev.pid");
267
355
  try {
268
356
  const pid = await fs.readFile(pidFile, "utf-8");
@@ -272,11 +360,13 @@ export function registerLifecycleCommands(program, _config) {
272
360
  console.log(pc.green(` ✓ Convex stopped (PID: ${pidNum})`));
273
361
  stoppedSomething = true;
274
362
  deploymentStopped = true;
363
+ convexStopped = true;
275
364
  }
276
365
  catch (e) {
277
366
  const err = e;
278
367
  if (err.code === "ESRCH") {
279
368
  console.log(pc.yellow(" Convex was already stopped"));
369
+ convexStopped = true; // Consider it handled
280
370
  }
281
371
  else {
282
372
  throw e;
@@ -290,13 +380,37 @@ export function registerLifecycleCommands(program, _config) {
290
380
  if (err.code !== "ENOENT") {
291
381
  console.error(pc.red(" Error stopping Convex:"), e);
292
382
  }
293
- else if (!options.graphOnly) {
294
- console.log(pc.dim(" No Convex process running"));
383
+ // No PID file - will try port-based detection below
384
+ }
385
+ // Fallback: try to stop by port if no PID file and deployment is local
386
+ if (!convexStopped) {
387
+ // Check if this is a local deployment (port 3210)
388
+ const deploymentConfig = Object.values(config.deployments || {}).find((d) => d.projectPath === projectPath);
389
+ const isLocal = deploymentConfig?.url?.includes("127.0.0.1:3210") ||
390
+ deploymentConfig?.url?.includes("localhost:3210");
391
+ if (isLocal) {
392
+ const convexPid = await findProcessByPort(3210);
393
+ if (convexPid) {
394
+ console.log(pc.yellow(` Found Convex on port 3210 (no PID file)`));
395
+ const stopped = await stopProcessByPort(3210);
396
+ if (stopped) {
397
+ console.log(pc.green(` ✓ Convex stopped (port 3210, PID: ${convexPid})`));
398
+ stoppedSomething = true;
399
+ deploymentStopped = true;
400
+ convexStopped = true;
401
+ }
402
+ }
403
+ else {
404
+ console.log(pc.dim(" No Convex process running"));
405
+ }
406
+ }
407
+ else if (!options.graphOnly && !options.appsOnly) {
408
+ console.log(pc.dim(" No Convex process running (PID file not found)"));
295
409
  }
296
410
  }
297
411
  }
298
- // Stop graph containers if not convex-only
299
- if (!options.convexOnly) {
412
+ // Stop graph containers if not convex-only and not apps-only
413
+ if (!options.convexOnly && !options.appsOnly) {
300
414
  const dockerComposePath = path.join(projectPath, "docker-compose.graph.yml");
301
415
  if (fs.existsSync(dockerComposePath)) {
302
416
  const stopped = await stopGraphContainers(projectPath);
@@ -309,17 +423,76 @@ export function registerLifecycleCommands(program, _config) {
309
423
  console.log(pc.dim(" No graph container running"));
310
424
  }
311
425
  }
312
- else if (!options.convexOnly) {
426
+ else if (!options.convexOnly && !options.appsOnly) {
313
427
  console.log(pc.dim(" No graph database configured"));
314
428
  }
315
429
  }
430
+ // Stop apps if not convex-only and not graph-only
431
+ if (!options.convexOnly && !options.graphOnly) {
432
+ const appPidFiles = await findAppPidFiles(projectPath);
433
+ const stoppedAppNames = new Set();
434
+ // First, try to stop apps with PID files
435
+ if (appPidFiles.length > 0) {
436
+ for (const { name: appName } of appPidFiles) {
437
+ const appStatus = await isAppRunning(appName, projectPath);
438
+ if (appStatus.running) {
439
+ const stopped = await stopApp(appName, projectPath);
440
+ if (stopped) {
441
+ console.log(pc.green(` ✓ App "${appName}" stopped (PID: ${appStatus.pid})`));
442
+ stoppedSomething = true;
443
+ stoppedAppsCount++;
444
+ deploymentStopped = true;
445
+ stoppedAppNames.add(appName);
446
+ }
447
+ }
448
+ else {
449
+ // Clean up stale PID file (already handled by isAppRunning)
450
+ console.log(pc.dim(` App "${appName}" was not running`));
451
+ }
452
+ }
453
+ }
454
+ // Also check configured apps that might not have PID files
455
+ // (started before PID tracking was added)
456
+ if (config.apps) {
457
+ for (const [appName, appConfig] of Object.entries(config.apps)) {
458
+ // Skip if already stopped via PID file or not matching this project
459
+ if (stoppedAppNames.has(appName))
460
+ continue;
461
+ if (appConfig.projectPath !== projectPath)
462
+ continue;
463
+ const port = appConfig.port || 3000;
464
+ const pid = await findProcessByPort(port);
465
+ if (pid) {
466
+ console.log(pc.yellow(` Found app "${appName}" on port ${port} (no PID file)`));
467
+ const stopped = await stopProcessByPort(port);
468
+ if (stopped) {
469
+ console.log(pc.green(` ✓ App "${appName}" stopped (port ${port}, PID: ${pid})`));
470
+ stoppedSomething = true;
471
+ stoppedAppsCount++;
472
+ deploymentStopped = true;
473
+ }
474
+ }
475
+ }
476
+ }
477
+ if (appPidFiles.length === 0 && !config.apps && options.appsOnly) {
478
+ console.log(pc.dim(" No apps configured"));
479
+ }
480
+ }
316
481
  if (deploymentStopped) {
317
482
  stoppedCount++;
318
483
  }
319
484
  console.log();
320
485
  }
486
+ // Summary
321
487
  if (stoppedSomething) {
322
- console.log(pc.green(` ✓ Stopped ${stoppedCount} deployment(s)\n`));
488
+ const parts = [];
489
+ if (stoppedCount > 0 && !options.appsOnly) {
490
+ parts.push(`${stoppedCount} deployment(s)`);
491
+ }
492
+ if (stoppedAppsCount > 0) {
493
+ parts.push(`${stoppedAppsCount} app(s)`);
494
+ }
495
+ console.log(pc.green(` ✓ Stopped ${parts.join(" and ")}\n`));
323
496
  }
324
497
  else {
325
498
  console.log(pc.yellow(" No services were running\n"));
@@ -368,8 +541,8 @@ export async function runInitWizard(targetDir, options = {}) {
368
541
  const projectInfo = await getProjectInfo(targetDir);
369
542
  // Step 2: Installation type
370
543
  const installationType = await getInstallationType(projectInfo.projectPath);
371
- // Step 3: Convex setup
372
- const convexConfig = await getConvexSetup(options);
544
+ // Step 3: Convex setup (pass project name for non-interactive setup)
545
+ const convexConfig = await getConvexSetup(options, projectInfo.projectName);
373
546
  // Step 4: Graph database (optional)
374
547
  let graphConfig = null;
375
548
  if (!options.skipGraph) {
@@ -387,6 +560,8 @@ export async function runInitWizard(targetDir, options = {}) {
387
560
  convexSetupType: convexConfig.type,
388
561
  convexUrl: convexConfig.config.convexUrl,
389
562
  deployKey: convexConfig.config.deployKey,
563
+ teamSlug: convexConfig.config.teamSlug,
564
+ sanitizedProjectName: convexConfig.config.sanitizedProjectName,
390
565
  graphEnabled: graphConfig !== null,
391
566
  graphType: graphConfig?.type || "skip",
392
567
  graphUri: graphConfig?.uri,
@@ -399,26 +574,64 @@ export async function runInitWizard(targetDir, options = {}) {
399
574
  if (!options.yes) {
400
575
  await showConfirmation(config);
401
576
  }
402
- // Execute setup
403
- await executeSetup(config);
404
- // Ask to start Convex if not already specified
577
+ // Execute setup (returns SDK metadata for post-setup steps)
578
+ const sdkMetadata = await executeSetup(config);
579
+ // Ask about Vercel AI quickstart (optional demo app)
580
+ let installedQuickstart = false;
581
+ let quickstartAppConfig = null;
582
+ if (!options.yes) {
583
+ const { installQuickstart } = await prompts({
584
+ type: "confirm",
585
+ name: "installQuickstart",
586
+ message: "Install Vercel AI quickstart demo?",
587
+ initial: false,
588
+ });
589
+ if (installQuickstart) {
590
+ // Need the actual Convex URL from the config that was just set up
591
+ const convexUrl = config.convexUrl || "";
592
+ // Pass graph config if it was enabled during init
593
+ const graphConfig = config.graphEnabled
594
+ ? {
595
+ enabled: true,
596
+ uri: config.graphUri,
597
+ username: config.graphUsername,
598
+ password: config.graphPassword,
599
+ }
600
+ : undefined;
601
+ quickstartAppConfig = await installVercelAIQuickstart(config.projectPath, sdkMetadata.sdkVersion, convexUrl, config.openaiApiKey, graphConfig);
602
+ // Save app to CLI config
603
+ const cliConfig = await loadConfig();
604
+ cliConfig.apps = cliConfig.apps || {};
605
+ cliConfig.apps["quickstart"] = quickstartAppConfig;
606
+ await saveUserConfig(cliConfig);
607
+ installedQuickstart = true;
608
+ }
609
+ }
610
+ // Context-aware start prompt
405
611
  let shouldStart = options.start;
406
612
  if (!shouldStart && !options.yes) {
613
+ const message = installedQuickstart
614
+ ? "Start Convex backend and quickstart app now?"
615
+ : "Start Convex development server now?";
407
616
  const response = await prompts({
408
617
  type: "confirm",
409
618
  name: "startNow",
410
- message: "Start Convex development server now?",
619
+ message,
411
620
  initial: true,
412
621
  });
413
622
  shouldStart = response.startNow;
414
623
  }
415
- // Start Convex if requested
624
+ // Start services if requested
416
625
  if (shouldStart) {
417
626
  const isLocal = config.convexSetupType === "local";
418
627
  await startConvexInBackground(config.projectPath, isLocal);
628
+ // Start quickstart app if installed
629
+ if (installedQuickstart && quickstartAppConfig) {
630
+ await startApp("quickstart", quickstartAppConfig);
631
+ }
419
632
  // Show running status dashboard
420
633
  console.log();
421
- await showRunningStatus(config.projectPath, isLocal);
634
+ await showRunningStatus(config.projectPath, isLocal, installedQuickstart);
422
635
  }
423
636
  }
424
637
  /**
@@ -477,49 +690,89 @@ async function getInstallationType(projectPath) {
477
690
  }
478
691
  /**
479
692
  * Get Convex setup configuration
693
+ *
694
+ * Handles auth, gets team slug, and sanitizes project name for non-interactive setup.
480
695
  */
481
- async function getConvexSetup(options) {
482
- // Quick options
696
+ async function getConvexSetup(options, projectName) {
697
+ console.log(pc.cyan("\n Convex Database Setup"));
698
+ console.log(pc.dim(" Cortex uses Convex as its backend database\n"));
699
+ // Ensure user is logged in and get team slug
700
+ const authStatus = await ensureConvexAuth();
701
+ const teamSlug = authStatus.teamSlug;
702
+ const sanitizedName = sanitizeProjectName(projectName);
703
+ // Quick options via CLI flags
483
704
  if (options.local) {
484
705
  return {
485
706
  type: "local",
486
- config: { convexUrl: "http://127.0.0.1:3210" },
707
+ config: {
708
+ convexUrl: "http://127.0.0.1:3210",
709
+ teamSlug,
710
+ sanitizedProjectName: sanitizedName,
711
+ },
487
712
  };
488
713
  }
489
714
  if (options.cloud) {
490
715
  return {
491
716
  type: "new",
492
- config: { convexUrl: "" },
717
+ config: {
718
+ convexUrl: "",
719
+ teamSlug,
720
+ sanitizedProjectName: sanitizedName,
721
+ },
493
722
  };
494
723
  }
495
- console.log(pc.cyan("\n Convex Database Setup"));
496
- console.log(pc.dim(" Cortex uses Convex as its backend database\n"));
724
+ // Build menu choices with project name context
725
+ const choices = [
726
+ {
727
+ title: "Local development (fast, recommended)",
728
+ description: `Creates "${sanitizedName}" with local backend`,
729
+ value: "local",
730
+ },
731
+ {
732
+ title: "Create new cloud project",
733
+ description: teamSlug
734
+ ? `Creates "${sanitizedName}" in team "${teamSlug}"`
735
+ : `Creates "${sanitizedName}" on Convex cloud`,
736
+ value: "new",
737
+ },
738
+ {
739
+ title: "Use existing Convex project",
740
+ description: "Connect to your existing deployment",
741
+ value: "existing",
742
+ },
743
+ ];
497
744
  const response = await prompts({
498
745
  type: "select",
499
746
  name: "setupType",
500
747
  message: "How would you like to set up Convex?",
501
- choices: [
502
- {
503
- title: "Create new Convex project",
504
- description: "Full features including vector search",
505
- value: "new",
506
- },
507
- {
508
- title: "Use existing Convex deployment",
509
- description: "Connect to your existing deployment",
510
- value: "existing",
511
- },
512
- ],
748
+ choices,
513
749
  initial: 0,
514
750
  });
515
751
  if (!response.setupType) {
516
752
  throw new Error("Convex setup is required");
517
753
  }
754
+ // For existing projects, ask for the project name
755
+ let existingProjectName = sanitizedName;
756
+ if (response.setupType === "existing") {
757
+ const existingResponse = await prompts({
758
+ type: "text",
759
+ name: "projectName",
760
+ message: "Enter the name of your existing Convex project:",
761
+ initial: sanitizedName,
762
+ validate: (value) => (value ? true : "Project name is required"),
763
+ });
764
+ if (!existingResponse.projectName) {
765
+ throw new Error("Project name is required");
766
+ }
767
+ existingProjectName = existingResponse.projectName;
768
+ }
518
769
  return {
519
770
  type: response.setupType,
520
771
  config: {
521
772
  convexUrl: "",
522
773
  deployKey: undefined,
774
+ teamSlug,
775
+ sanitizedProjectName: response.setupType === "existing" ? existingProjectName : sanitizedName,
523
776
  },
524
777
  };
525
778
  }
@@ -601,6 +854,7 @@ async function showConfirmation(config) {
601
854
  }
602
855
  /**
603
856
  * Execute the setup
857
+ * Returns SDK metadata for use in post-setup steps (like quickstart installation)
604
858
  */
605
859
  async function executeSetup(config) {
606
860
  console.log(pc.cyan("\n Setting up Cortex...\n"));
@@ -648,15 +902,46 @@ async function executeSetup(config) {
648
902
  await createConvexJson(config.projectPath);
649
903
  backendSpinner.succeed("Cortex backend files ready");
650
904
  // Setup and deploy Convex in ONE step
905
+ // Pass teamSlug and projectName for non-interactive setup
906
+ // IMPORTANT: Always pass useLocalBackend when local mode is selected,
907
+ // even if teamSlug/projectName are missing (falls back to interactive)
651
908
  let convexConfig;
652
- if (config.convexSetupType === "new") {
653
- // For new projects: prompt for project creation and deploy everything
654
- convexConfig = await setupNewConvex(config.projectPath);
909
+ const isLocalMode = config.convexSetupType === "local";
910
+ const convexOptions = config.teamSlug && config.sanitizedProjectName
911
+ ? {
912
+ teamSlug: config.teamSlug,
913
+ projectName: config.sanitizedProjectName,
914
+ useLocalBackend: isLocalMode,
915
+ }
916
+ : isLocalMode
917
+ ? { useLocalBackend: true }
918
+ : undefined;
919
+ if (config.convexSetupType === "new" ||
920
+ config.convexSetupType === "local") {
921
+ // For new projects (cloud or local): create project and deploy
922
+ convexConfig = await setupNewConvex(config.projectPath, convexOptions);
655
923
  }
656
924
  else {
657
- // For existing projects: get URL/key, then deploy
658
- convexConfig = await setupExistingConvex();
659
- await deployToConvex(config.projectPath, convexConfig, false);
925
+ // For existing projects: connect and deploy
926
+ try {
927
+ convexConfig = await setupExistingConvex(config.projectPath, convexOptions
928
+ ? {
929
+ teamSlug: convexOptions.teamSlug,
930
+ projectName: convexOptions.projectName,
931
+ }
932
+ : undefined);
933
+ // Only deploy if we used interactive mode (no options)
934
+ if (!convexOptions) {
935
+ await deployToConvex(config.projectPath, convexConfig, false);
936
+ }
937
+ }
938
+ catch (error) {
939
+ // The Convex CLI already printed the actual error (e.g., "Project not found",
940
+ // "Authentication failed", "Network error", etc.) to the console.
941
+ // Just provide a helpful hint without assuming the cause.
942
+ console.log(pc.dim("\n If the project wasn't found, check for typos or select 'Create new' instead."));
943
+ throw error;
944
+ }
660
945
  }
661
946
  // Update config with actual Convex details
662
947
  config.convexUrl = convexConfig.convexUrl;
@@ -715,6 +1000,11 @@ async function executeSetup(config) {
715
1000
  await saveDeploymentToConfig(config);
716
1001
  // Success!
717
1002
  showSuccessMessage(config);
1003
+ // Return SDK metadata for use in post-setup steps
1004
+ return {
1005
+ sdkVersion: sdkMetadata.sdkVersion,
1006
+ convexVersion: sdkMetadata.convexVersion,
1007
+ };
718
1008
  }
719
1009
  catch (error) {
720
1010
  console.error(pc.red("\n Setup failed:"), error);
@@ -736,11 +1026,12 @@ async function saveDeploymentToConfig(config) {
736
1026
  const deploymentName = config.projectName
737
1027
  .toLowerCase()
738
1028
  .replace(/[^a-z0-9-]/g, "-");
739
- // Add the deployment
1029
+ // Add the deployment (enabled by default when created by init)
740
1030
  userConfig.deployments[deploymentName] = {
741
1031
  url: config.convexUrl,
742
1032
  key: config.deployKey,
743
1033
  projectPath: config.projectPath,
1034
+ enabled: true,
744
1035
  };
745
1036
  // Set as default if no default exists
746
1037
  if (!userConfig.default) {
@@ -861,7 +1152,7 @@ async function startConvexInBackground(projectPath, isLocal, deploymentUrl, depl
861
1152
  /**
862
1153
  * Show running services status dashboard
863
1154
  */
864
- async function showRunningStatus(projectPath, isLocal) {
1155
+ async function showRunningStatus(projectPath, isLocal, hasQuickstart = false) {
865
1156
  const width = 56;
866
1157
  const line = "═".repeat(width);
867
1158
  const thinLine = "─".repeat(width);
@@ -933,6 +1224,15 @@ async function showRunningStatus(projectPath, isLocal) {
933
1224
  console.log(` ${pc.dim("○")} Not configured`);
934
1225
  }
935
1226
  console.log();
1227
+ // Quickstart App Status (if installed)
1228
+ if (hasQuickstart) {
1229
+ console.log(pc.bold(pc.white(" Vercel AI Quickstart")));
1230
+ console.log(pc.dim(" " + thinLine));
1231
+ console.log(` ${pc.green("●")} Running`);
1232
+ console.log(pc.dim(" URL: http://localhost:3000"));
1233
+ console.log(pc.dim(" Interactive chat demo with memory visualization"));
1234
+ console.log();
1235
+ }
936
1236
  // Quick Actions
937
1237
  console.log(pc.bold(pc.white(" Quick Actions")));
938
1238
  console.log(pc.dim(" " + thinLine));
@@ -941,6 +1241,9 @@ async function showRunningStatus(projectPath, isLocal) {
941
1241
  pc.dim(" # Foreground mode (see logs)"));
942
1242
  console.log(pc.dim(" cortex stop") +
943
1243
  pc.dim(" # Stop background services"));
1244
+ if (hasQuickstart) {
1245
+ console.log(pc.dim(" npm run quickstart") + pc.dim(" # Start quickstart demo"));
1246
+ }
944
1247
  console.log(pc.dim(" npm start") + pc.dim(" # Run your AI agent"));
945
1248
  console.log();
946
1249
  // Log file hint