@agentuity/cli 0.0.108 → 0.0.110

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 (87) hide show
  1. package/dist/build-report.d.ts +201 -0
  2. package/dist/build-report.d.ts.map +1 -0
  3. package/dist/build-report.js +335 -0
  4. package/dist/build-report.js.map +1 -0
  5. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  6. package/dist/cmd/build/entry-generator.js +9 -3
  7. package/dist/cmd/build/entry-generator.js.map +1 -1
  8. package/dist/cmd/build/index.d.ts.map +1 -1
  9. package/dist/cmd/build/index.js +44 -1
  10. package/dist/cmd/build/index.js.map +1 -1
  11. package/dist/cmd/build/typecheck.d.ts +7 -1
  12. package/dist/cmd/build/typecheck.d.ts.map +1 -1
  13. package/dist/cmd/build/typecheck.js +11 -1
  14. package/dist/cmd/build/typecheck.js.map +1 -1
  15. package/dist/cmd/build/vite/index.d.ts +2 -1
  16. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  17. package/dist/cmd/build/vite/index.js +2 -1
  18. package/dist/cmd/build/vite/index.js.map +1 -1
  19. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  20. package/dist/cmd/build/vite/metadata-generator.js +2 -4
  21. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  22. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  23. package/dist/cmd/build/vite/registry-generator.js +56 -18
  24. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  25. package/dist/cmd/build/vite/vite-builder.d.ts +3 -0
  26. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  27. package/dist/cmd/build/vite/vite-builder.js +14 -1
  28. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  29. package/dist/cmd/build/vite-bundler.d.ts +3 -0
  30. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  31. package/dist/cmd/build/vite-bundler.js +14 -5
  32. package/dist/cmd/build/vite-bundler.js.map +1 -1
  33. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  34. package/dist/cmd/cloud/deploy.js +86 -9
  35. package/dist/cmd/cloud/deploy.js.map +1 -1
  36. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  37. package/dist/cmd/cloud/deployment/show.js +0 -1
  38. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  39. package/dist/cmd/dev/index.d.ts.map +1 -1
  40. package/dist/cmd/dev/index.js +109 -15
  41. package/dist/cmd/dev/index.js.map +1 -1
  42. package/dist/cmd/project/auth/generate.d.ts +5 -0
  43. package/dist/cmd/project/auth/generate.d.ts.map +1 -0
  44. package/dist/cmd/project/auth/generate.js +102 -0
  45. package/dist/cmd/project/auth/generate.js.map +1 -0
  46. package/dist/cmd/project/auth/index.d.ts +2 -0
  47. package/dist/cmd/project/auth/index.d.ts.map +1 -0
  48. package/dist/cmd/project/auth/index.js +21 -0
  49. package/dist/cmd/project/auth/index.js.map +1 -0
  50. package/dist/cmd/project/auth/init.d.ts +2 -0
  51. package/dist/cmd/project/auth/init.d.ts.map +1 -0
  52. package/dist/cmd/project/auth/init.js +220 -0
  53. package/dist/cmd/project/auth/init.js.map +1 -0
  54. package/dist/cmd/project/auth/shared.d.ts +88 -0
  55. package/dist/cmd/project/auth/shared.d.ts.map +1 -0
  56. package/dist/cmd/project/auth/shared.js +435 -0
  57. package/dist/cmd/project/auth/shared.js.map +1 -0
  58. package/dist/cmd/project/index.d.ts.map +1 -1
  59. package/dist/cmd/project/index.js +9 -1
  60. package/dist/cmd/project/index.js.map +1 -1
  61. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  62. package/dist/cmd/project/template-flow.js +106 -0
  63. package/dist/cmd/project/template-flow.js.map +1 -1
  64. package/dist/types.d.ts +1 -3
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js +1 -2
  67. package/dist/types.js.map +1 -1
  68. package/package.json +5 -4
  69. package/src/build-report.ts +457 -0
  70. package/src/cmd/build/entry-generator.ts +9 -3
  71. package/src/cmd/build/index.ts +51 -1
  72. package/src/cmd/build/typecheck.ts +19 -1
  73. package/src/cmd/build/vite/index.ts +4 -1
  74. package/src/cmd/build/vite/metadata-generator.ts +4 -6
  75. package/src/cmd/build/vite/registry-generator.ts +58 -19
  76. package/src/cmd/build/vite/vite-builder.ts +18 -1
  77. package/src/cmd/build/vite-bundler.ts +17 -4
  78. package/src/cmd/cloud/deploy.ts +105 -11
  79. package/src/cmd/cloud/deployment/show.ts +0 -1
  80. package/src/cmd/dev/index.ts +115 -14
  81. package/src/cmd/project/auth/generate.ts +116 -0
  82. package/src/cmd/project/auth/index.ts +21 -0
  83. package/src/cmd/project/auth/init.ts +263 -0
  84. package/src/cmd/project/auth/shared.ts +534 -0
  85. package/src/cmd/project/index.ts +9 -1
  86. package/src/cmd/project/template-flow.ts +125 -0
  87. package/src/types.ts +1 -2
@@ -371,6 +371,7 @@ export const command = createCommand({
371
371
  // Vite stays running and handles frontend changes via HMR
372
372
  let shouldRestart = false;
373
373
  let gravityProcess: ProcessLike | null = null;
374
+ let gravityHeartbeatInterval: ReturnType<typeof setInterval> | null = null;
374
375
  let stdinListenerRegistered = false; // Track if stdin listener is already registered
375
376
 
376
377
  const restartServer = () => {
@@ -395,6 +396,8 @@ export const command = createCommand({
395
396
  let cleaningUp = false;
396
397
  // Track if shutdown was requested (SIGINT/SIGTERM) to break the main loop
397
398
  let shutdownRequested = false;
399
+ // Store stdin data handler reference for cleanup
400
+ let stdinDataHandler: ((data: Buffer | string) => void) | null = null;
398
401
 
399
402
  /**
400
403
  * Centralized cleanup function for all resources.
@@ -425,6 +428,12 @@ export const command = createCommand({
425
428
  logger.debug('Error stopping Bun server during cleanup: %s', err);
426
429
  }
427
430
 
431
+ // Stop gravity heartbeat interval
432
+ if (gravityHeartbeatInterval) {
433
+ clearInterval(gravityHeartbeatInterval);
434
+ gravityHeartbeatInterval = null;
435
+ }
436
+
428
437
  // Kill gravity client with SIGTERM first, then SIGKILL as fallback
429
438
  if (gravityProcess) {
430
439
  logger.debug('Killing gravity process...');
@@ -479,6 +488,21 @@ export const command = createCommand({
479
488
  if (!exitAfter) {
480
489
  cleaningUp = false;
481
490
  } else {
491
+ // Clean up stdin keyboard handler right before exiting
492
+ // This must happen AFTER all async cleanup to keep event loop alive
493
+ if (stdinListenerRegistered && process.stdin.isTTY) {
494
+ try {
495
+ if (stdinDataHandler) {
496
+ process.stdin.removeListener('data', stdinDataHandler);
497
+ stdinDataHandler = null;
498
+ }
499
+ process.stdin.setRawMode(false);
500
+ process.stdin.pause();
501
+ process.stdin.unref();
502
+ } catch {
503
+ // Ignore errors during final cleanup
504
+ }
505
+ }
482
506
  logger.debug('Exiting with code %d', exitCode);
483
507
  originalExit(exitCode);
484
508
  }
@@ -497,6 +521,12 @@ export const command = createCommand({
497
521
  logger.debug('Error stopping Bun server for restart: %s', err);
498
522
  }
499
523
 
524
+ // Stop gravity heartbeat interval
525
+ if (gravityHeartbeatInterval) {
526
+ clearInterval(gravityHeartbeatInterval);
527
+ gravityHeartbeatInterval = null;
528
+ }
529
+
500
530
  // Kill gravity client
501
531
  if (gravityProcess) {
502
532
  try {
@@ -515,23 +545,37 @@ export const command = createCommand({
515
545
 
516
546
  // SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
517
547
  let signalHandlersRegistered = false;
548
+ let exitingFromSignal = false;
518
549
  if (!signalHandlersRegistered) {
519
550
  signalHandlersRegistered = true;
520
551
 
521
- const safeExit = async (code: number, reason?: string) => {
552
+ const safeExit = (code: number, reason?: string) => {
553
+ // Prevent multiple signal handlers from racing
554
+ if (exitingFromSignal) return;
555
+ exitingFromSignal = true;
556
+
522
557
  if (reason) {
523
558
  logger.debug('DevMode terminating (%d) due to: %s', code, reason);
524
559
  }
525
560
  shutdownRequested = true;
526
- await cleanup(true, code);
561
+ // Run cleanup and ensure we wait for it to complete before exiting
562
+ cleanup(true, code).catch((err) => {
563
+ logger.debug('Cleanup error: %s', err);
564
+ originalExit(1);
565
+ });
527
566
  };
528
567
 
529
568
  process.on('SIGINT', () => {
530
- void safeExit(0, 'SIGINT');
569
+ safeExit(0, 'SIGINT');
531
570
  });
532
571
 
533
572
  process.on('SIGTERM', () => {
534
- void safeExit(0, 'SIGTERM');
573
+ safeExit(0, 'SIGTERM');
574
+ });
575
+
576
+ // Handle SIGHUP (terminal closed) - same as SIGINT
577
+ process.on('SIGHUP', () => {
578
+ safeExit(0, 'SIGHUP');
535
579
  });
536
580
 
537
581
  // Handle uncaught exceptions - clean up and exit rather than limping on
@@ -553,6 +597,20 @@ export const command = createCommand({
553
597
 
554
598
  // Ensure resources are always cleaned up on exit (synchronous fallback)
555
599
  process.on('exit', () => {
600
+ // Clean up stdin keyboard handler
601
+ if (stdinListenerRegistered && process.stdin.isTTY) {
602
+ try {
603
+ if (stdinDataHandler) {
604
+ process.stdin.removeListener('data', stdinDataHandler);
605
+ }
606
+ process.stdin.setRawMode(false);
607
+ process.stdin.pause();
608
+ process.stdin.unref();
609
+ } catch {
610
+ // Ignore errors during exit cleanup
611
+ }
612
+ }
613
+
556
614
  // Kill gravity client with SIGKILL for immediate termination
557
615
  if (gravityProcess && gravityProcess.exitCode === null) {
558
616
  try {
@@ -862,6 +920,7 @@ export const command = createCommand({
862
920
  project.projectId,
863
921
  '--token',
864
922
  process.env.AGENTUITY_SDK_KEY!, // set above
923
+ '--health-check',
865
924
  ],
866
925
  {
867
926
  cwd: rootDir,
@@ -881,13 +940,46 @@ export const command = createCommand({
881
940
  });
882
941
  }
883
942
 
884
- // Log gravity output
943
+ // Log gravity output and detect heartbeat port
885
944
  (async () => {
886
945
  try {
887
946
  if (gravityProcess?.stdout) {
888
947
  for await (const chunk of gravityProcess.stdout) {
889
948
  const text = new TextDecoder().decode(chunk);
890
- logger.debug('[gravity] %s', text.trim());
949
+ const trimmed = text.trim();
950
+
951
+ // Check for heartbeat port announcement
952
+ const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
953
+ if (match) {
954
+ const heartbeatPort = parseInt(match[1], 10);
955
+ logger.debug('Gravity heartbeat port detected: %d', heartbeatPort);
956
+
957
+ // Start sending heartbeats every 5 seconds
958
+ if (!gravityHeartbeatInterval) {
959
+ const sendHeartbeat = async () => {
960
+ try {
961
+ await fetch(
962
+ `http://127.0.0.1:${heartbeatPort}/heartbeat`,
963
+ {
964
+ method: 'POST',
965
+ signal: AbortSignal.timeout(2000),
966
+ }
967
+ );
968
+ logger.trace('Gravity heartbeat sent');
969
+ } catch (err) {
970
+ logger.trace('Gravity heartbeat failed: %s', err);
971
+ }
972
+ };
973
+
974
+ // Send initial heartbeat immediately
975
+ sendHeartbeat();
976
+
977
+ // Then send every 5 seconds
978
+ gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
979
+ }
980
+ } else if (trimmed) {
981
+ logger.debug('[gravity] %s', trimmed);
982
+ }
891
983
  }
892
984
  }
893
985
  } catch (err) {
@@ -930,12 +1022,23 @@ export const command = createCommand({
930
1022
  console.log(tui.muted(' q') + ' - quit\n');
931
1023
  };
932
1024
 
933
- process.stdin.on('data', (data) => {
1025
+ // Store handler reference for cleanup
1026
+ stdinDataHandler = (data) => {
934
1027
  const key = data.toString();
935
1028
 
936
- // Handle Ctrl+C - send SIGINT to trigger graceful shutdown
937
- if (key === '\u0003') {
938
- process.kill(process.pid, 'SIGINT');
1029
+ // Handle Ctrl+C or q - trigger graceful shutdown
1030
+ if (key === '\u0003' || key === 'q') {
1031
+ // Remove stdin listener immediately to prevent re-entrancy
1032
+ if (stdinDataHandler) {
1033
+ process.stdin.removeListener('data', stdinDataHandler);
1034
+ stdinDataHandler = null;
1035
+ }
1036
+ // Set shutdown flag and trigger cleanup directly
1037
+ shutdownRequested = true;
1038
+ cleanup(true, 0).catch((err) => {
1039
+ logger.debug('Cleanup error: %s', err);
1040
+ originalExit(1);
1041
+ });
939
1042
  return;
940
1043
  }
941
1044
 
@@ -952,14 +1055,12 @@ export const command = createCommand({
952
1055
  centerTitle: false,
953
1056
  });
954
1057
  break;
955
- case 'q':
956
- void cleanup(true, 0);
957
- break;
958
1058
  default:
959
1059
  process.stdout.write(data);
960
1060
  break;
961
1061
  }
962
- });
1062
+ };
1063
+ process.stdin.on('data', stdinDataHandler);
963
1064
  }
964
1065
 
965
1066
  showWelcome();
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Generate auth schema SQL using drizzle-kit export
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import { createSubcommand } from '../../../types';
9
+ import * as tui from '../../../tui';
10
+ import { getCommand } from '../../../command-prefix';
11
+ import { generateAuthSchemaSql, getGeneratedSqlDir } from './shared';
12
+
13
+ export const generateSubcommand = createSubcommand({
14
+ name: 'generate',
15
+ description: 'Generate SQL schema for Agentuity Auth tables',
16
+ tags: ['slow'],
17
+ requires: { project: true },
18
+ examples: [
19
+ {
20
+ command: getCommand('project auth generate'),
21
+ description: 'Generate SQL schema and save to agentuity-auth-schema.sql',
22
+ },
23
+ {
24
+ command: getCommand('project auth generate --output ./migrations/auth.sql'),
25
+ description: 'Generate schema to a custom path',
26
+ },
27
+ {
28
+ command: getCommand('project auth generate --output -'),
29
+ description: 'Output SQL to stdout',
30
+ },
31
+ ],
32
+ schema: {
33
+ options: z.object({
34
+ output: z
35
+ .string()
36
+ .optional()
37
+ .describe(
38
+ 'Output path for generated SQL (default: ./agentuity-auth-schema.sql). Use "-" for stdout.'
39
+ ),
40
+ }),
41
+ response: z.object({
42
+ success: z.boolean().describe('Whether generation succeeded'),
43
+ outputPath: z.string().optional().describe('Path where SQL was written'),
44
+ }),
45
+ },
46
+
47
+ async handler(ctx) {
48
+ const { logger, opts, projectDir, options } = ctx;
49
+ const explicitOutput = opts?.output as string | undefined;
50
+ const toStdout = explicitOutput === '-';
51
+ const isJson = options?.json === true;
52
+
53
+ if (!toStdout && !isJson) {
54
+ tui.newline();
55
+ tui.info(tui.bold('Agentuity Auth Schema Generation'));
56
+ tui.newline();
57
+ }
58
+
59
+ try {
60
+ const sql = isJson
61
+ ? await generateAuthSchemaSql(projectDir)
62
+ : await tui.spinner({
63
+ message: 'Generating auth schema SQL from Drizzle schema',
64
+ clearOnSuccess: true,
65
+ callback: () => generateAuthSchemaSql(projectDir),
66
+ });
67
+
68
+ if (toStdout) {
69
+ console.log(sql);
70
+ return { success: true };
71
+ }
72
+
73
+ let outputPath: string;
74
+ let displayPath: string;
75
+
76
+ if (explicitOutput) {
77
+ outputPath = path.resolve(projectDir, explicitOutput);
78
+ displayPath = explicitOutput;
79
+ } else {
80
+ const sqlOutputDir = await getGeneratedSqlDir(projectDir);
81
+ const sqlFileName = 'agentuity-auth-schema.sql';
82
+ outputPath = path.join(sqlOutputDir, sqlFileName);
83
+ displayPath =
84
+ sqlOutputDir === projectDir ? sqlFileName : path.relative(projectDir, outputPath);
85
+ }
86
+
87
+ fs.writeFileSync(outputPath, sql);
88
+
89
+ if (!isJson) {
90
+ tui.success(`Auth schema SQL saved to ${tui.bold(displayPath)}`);
91
+ tui.newline();
92
+ tui.info('Next steps:');
93
+ console.log(' 1. Review the generated SQL file');
94
+ console.log(' 2. Run the SQL against your database');
95
+ console.log(` ${tui.muted('Or use: agentuity project auth init')}`);
96
+ tui.newline();
97
+ }
98
+
99
+ return { success: true, outputPath };
100
+ } catch (error) {
101
+ logger.error('Schema generation failed', { error });
102
+
103
+ if (!isJson) {
104
+ tui.error(
105
+ `Schema generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
106
+ );
107
+ tui.newline();
108
+ tui.info('Make sure you have:');
109
+ console.log(' 1. @agentuity/auth installed as a dependency');
110
+ console.log(' 2. drizzle-kit available (installed with @agentuity/auth)');
111
+ }
112
+
113
+ return { success: false };
114
+ }
115
+ },
116
+ });
@@ -0,0 +1,21 @@
1
+ import { createCommand } from '../../../types';
2
+ import { initSubcommand } from './init';
3
+ import { generateSubcommand } from './generate';
4
+ import { getCommand } from '../../../command-prefix';
5
+
6
+ export const authCommand = createCommand({
7
+ name: 'auth',
8
+ description: 'Manage project authentication (Agentuity Auth)',
9
+ tags: ['slow', 'requires-auth'],
10
+ examples: [
11
+ {
12
+ command: getCommand('project auth init'),
13
+ description: 'Set up Agentuity Auth for an existing project',
14
+ },
15
+ {
16
+ command: getCommand('project auth generate'),
17
+ description: 'Generate SQL schema for auth tables',
18
+ },
19
+ ],
20
+ subcommands: [initSubcommand, generateSubcommand],
21
+ });
@@ -0,0 +1,263 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand } from '../../../types';
3
+ import * as tui from '../../../tui';
4
+ import { getCommand } from '../../../command-prefix';
5
+ import {
6
+ selectOrCreateDatabase,
7
+ ensureAuthDependencies,
8
+ runAuthMigrations,
9
+ generateAuthFileContent,
10
+ printIntegrationExamples,
11
+ detectOrmSetup,
12
+ generateAuthSchemaSql,
13
+ getGeneratedSqlDir,
14
+ } from './shared';
15
+ import enquirer from 'enquirer';
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+
19
+ export const initSubcommand = createSubcommand({
20
+ name: 'init',
21
+ description: 'Set up Agentuity Auth for your project',
22
+ tags: ['mutating', 'slow', 'requires-auth'],
23
+ requires: { auth: true, org: true, region: true },
24
+ idempotent: false,
25
+ examples: [
26
+ {
27
+ command: getCommand('project auth init'),
28
+ description: 'Set up Agentuity Auth with database selection',
29
+ },
30
+ ],
31
+ schema: {
32
+ options: z.object({
33
+ skipMigrations: z
34
+ .boolean()
35
+ .optional()
36
+ .describe(
37
+ 'Skip running database migrations (run `agentuity project auth generate` later)'
38
+ ),
39
+ }),
40
+ response: z.object({
41
+ success: z.boolean().describe('Whether setup succeeded'),
42
+ database: z.string().optional().describe('Database name used'),
43
+ authFileCreated: z.boolean().describe('Whether auth.ts was created'),
44
+ migrationsRun: z.boolean().describe('Whether migrations were run'),
45
+ }),
46
+ },
47
+
48
+ async handler(ctx) {
49
+ const { logger, opts, auth, orgId, region } = ctx;
50
+
51
+ tui.newline();
52
+ tui.info(tui.bold('Agentuity Auth Setup'));
53
+ tui.newline();
54
+ tui.info('This will:');
55
+ console.log(' • Ensure you have a Postgres database configured');
56
+ console.log(' • Install @agentuity/auth');
57
+ console.log(' • Run database migrations to create auth tables');
58
+ console.log(' • Show you how to wire auth into your API and UI');
59
+ tui.newline();
60
+
61
+ const projectDir = process.cwd();
62
+
63
+ // Check for package.json
64
+ const packageJsonPath = path.join(projectDir, 'package.json');
65
+ if (!fs.existsSync(packageJsonPath)) {
66
+ tui.fatal('No package.json found. Run this command from your project root.');
67
+ }
68
+
69
+ // Step 1: Check for DATABASE_URL or select/create database
70
+ let databaseUrl = process.env.DATABASE_URL;
71
+
72
+ if (!databaseUrl) {
73
+ // Check .env file
74
+ const envPath = path.join(projectDir, '.env');
75
+ if (fs.existsSync(envPath)) {
76
+ const envContent = fs.readFileSync(envPath, 'utf-8');
77
+ const match = envContent.match(/^DATABASE_URL=(.+)$/m);
78
+ if (match) {
79
+ databaseUrl = match[1].trim().replace(/^["']|["']$/g, '');
80
+ }
81
+ }
82
+ }
83
+
84
+ // Show database picker (with existing as first option if configured)
85
+ const dbInfo = await selectOrCreateDatabase({
86
+ logger,
87
+ auth,
88
+ orgId,
89
+ region,
90
+ existingUrl: databaseUrl,
91
+ });
92
+
93
+ const databaseName = dbInfo.name;
94
+
95
+ // Update .env with database URL
96
+ const envPath = path.join(projectDir, '.env');
97
+ let envContent = '';
98
+
99
+ if (fs.existsSync(envPath)) {
100
+ envContent = fs.readFileSync(envPath, 'utf-8');
101
+ if (!envContent.endsWith('\n') && envContent.length > 0) {
102
+ envContent += '\n';
103
+ }
104
+ }
105
+
106
+ // Check if DATABASE_URL already exists
107
+ const hasDatabaseUrl = envContent.match(/^DATABASE_URL=/m);
108
+
109
+ if (dbInfo.url !== databaseUrl || !hasDatabaseUrl) {
110
+ if (hasDatabaseUrl) {
111
+ // DATABASE_URL exists, use AUTH_DATABASE_URL instead
112
+ envContent += `AUTH_DATABASE_URL="${dbInfo.url}"\n`;
113
+ fs.writeFileSync(envPath, envContent);
114
+ tui.success('AUTH_DATABASE_URL added to .env');
115
+ tui.warning(
116
+ `DATABASE_URL already exists. Update your ${tui.bold('src/auth.ts')} to use AUTH_DATABASE_URL.`
117
+ );
118
+ } else {
119
+ envContent += `DATABASE_URL="${dbInfo.url}"\n`;
120
+ fs.writeFileSync(envPath, envContent);
121
+ tui.success('DATABASE_URL added to .env');
122
+ }
123
+ } else {
124
+ tui.success(`Using database: ${databaseName}`);
125
+ }
126
+
127
+ // Add AGENTUITY_AUTH_SECRET if not present
128
+ // Re-read envContent to get latest state
129
+ envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
130
+ if (!envContent.endsWith('\n') && envContent.length > 0) {
131
+ envContent += '\n';
132
+ }
133
+
134
+ const hasAuthSecret =
135
+ envContent.match(/^AGENTUITY_AUTH_SECRET=/m) || envContent.match(/^BETTER_AUTH_SECRET=/m);
136
+ if (!hasAuthSecret) {
137
+ const devSecret = `dev-${crypto.randomUUID()}-CHANGE-ME`;
138
+ envContent += `AGENTUITY_AUTH_SECRET="${devSecret}"\n`;
139
+ fs.writeFileSync(envPath, envContent);
140
+ tui.success('AGENTUITY_AUTH_SECRET added to .env (development default)');
141
+ tui.warning(
142
+ `Replace ${tui.bold('AGENTUITY_AUTH_SECRET')} with a secure value before deploying.`
143
+ );
144
+ tui.info(`Generate one with: ${tui.muted('openssl rand -hex 32')}`);
145
+ }
146
+
147
+ // Step 2: Install dependencies
148
+ tui.newline();
149
+ await ensureAuthDependencies({ projectDir, logger });
150
+
151
+ // Step 3: Generate auth.ts if it doesn't exist
152
+ const authFilePath = path.join(projectDir, 'src', 'auth.ts');
153
+ let authFileCreated = false;
154
+
155
+ if (fs.existsSync(authFilePath)) {
156
+ tui.info('src/auth.ts already exists, skipping generation');
157
+ } else {
158
+ const { createFile } = await enquirer.prompt<{ createFile: boolean }>({
159
+ type: 'confirm',
160
+ name: 'createFile',
161
+ message: 'Create src/auth.ts with default configuration?',
162
+ initial: true,
163
+ });
164
+
165
+ if (createFile) {
166
+ // Ensure src directory exists
167
+ const srcDir = path.join(projectDir, 'src');
168
+ if (!fs.existsSync(srcDir)) {
169
+ fs.mkdirSync(srcDir, { recursive: true });
170
+ }
171
+
172
+ fs.writeFileSync(authFilePath, generateAuthFileContent());
173
+ tui.success('Created src/auth.ts');
174
+ authFileCreated = true;
175
+ }
176
+ }
177
+
178
+ // Step 4: Run migrations (ORM-aware)
179
+ let migrationsRun = false;
180
+
181
+ if (opts.skipMigrations) {
182
+ tui.info('Skipping migrations (run `agentuity project auth generate` later)');
183
+ } else if (databaseName) {
184
+ tui.newline();
185
+
186
+ const ormSetup = await detectOrmSetup(projectDir);
187
+
188
+ if (ormSetup === 'drizzle') {
189
+ tui.info(tui.bold('Drizzle detected in your project.'));
190
+ tui.newline();
191
+ console.log(
192
+ ' Since you manage your own Drizzle schema, add authSchema to your schema:'
193
+ );
194
+ tui.newline();
195
+ console.log(tui.muted(" import * as authSchema from '@agentuity/auth/schema';"));
196
+ console.log(tui.muted(' export const schema = { ...authSchema, ...yourSchema };'));
197
+ tui.newline();
198
+ console.log(' Then run migrations:');
199
+ console.log(tui.muted(' bunx drizzle-kit push'));
200
+ tui.newline();
201
+ } else if (ormSetup === 'prisma') {
202
+ tui.info(tui.bold('Prisma detected in your project.'));
203
+ tui.newline();
204
+
205
+ const sql = await tui.spinner({
206
+ message: 'Preparing auth database schema...',
207
+ clearOnSuccess: true,
208
+ callback: () => generateAuthSchemaSql(projectDir),
209
+ });
210
+
211
+ const sqlOutputDir = await getGeneratedSqlDir(projectDir);
212
+ const sqlFileName = 'agentuity-auth-schema.sql';
213
+ const sqlFilePath = path.join(sqlOutputDir, sqlFileName);
214
+ const relativePath =
215
+ sqlOutputDir === projectDir ? sqlFileName : path.relative(projectDir, sqlFilePath);
216
+ fs.writeFileSync(sqlFilePath, sql);
217
+ tui.success(`Auth schema SQL saved to ${tui.bold(relativePath)}`);
218
+ tui.newline();
219
+ console.log(' Run this SQL against your database to create auth tables.');
220
+ tui.newline();
221
+ } else {
222
+ const { runMigrations } = await enquirer.prompt<{ runMigrations: boolean }>({
223
+ type: 'confirm',
224
+ name: 'runMigrations',
225
+ message: 'Run database migrations now? (idempotent, safe to re-run)',
226
+ initial: true,
227
+ });
228
+
229
+ if (runMigrations) {
230
+ const sql = await tui.spinner({
231
+ message: 'Preparing auth database schema...',
232
+ clearOnSuccess: true,
233
+ callback: () => generateAuthSchemaSql(projectDir),
234
+ });
235
+
236
+ await runAuthMigrations({
237
+ logger,
238
+ auth,
239
+ orgId,
240
+ region,
241
+ databaseName,
242
+ sql,
243
+ });
244
+ migrationsRun = true;
245
+ }
246
+ }
247
+ } else {
248
+ tui.warning(
249
+ 'Could not determine database name. Run `agentuity project auth generate` manually.'
250
+ );
251
+ }
252
+
253
+ // Step 5: Print integration examples
254
+ printIntegrationExamples();
255
+
256
+ return {
257
+ success: true,
258
+ database: databaseName,
259
+ authFileCreated,
260
+ migrationsRun,
261
+ };
262
+ },
263
+ });