@agentuity/cli 0.0.100 → 0.0.101

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 (94) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/api.d.ts +1 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -1
  5. package/dist/api.js.map +1 -1
  6. package/dist/cmd/build/ast.d.ts +2 -1
  7. package/dist/cmd/build/ast.d.ts.map +1 -1
  8. package/dist/cmd/build/ast.js +135 -47
  9. package/dist/cmd/build/ast.js.map +1 -1
  10. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  11. package/dist/cmd/build/entry-generator.js +220 -188
  12. package/dist/cmd/build/entry-generator.js.map +1 -1
  13. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/agent-discovery.js +103 -45
  15. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  16. package/dist/cmd/build/vite/bun-dev-server.js +1 -1
  17. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  18. package/dist/cmd/build/vite/docs-generator.d.ts +13 -0
  19. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -0
  20. package/dist/cmd/build/vite/docs-generator.js +81 -0
  21. package/dist/cmd/build/vite/docs-generator.js.map +1 -0
  22. package/dist/cmd/build/vite/index.d.ts +3 -3
  23. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  24. package/dist/cmd/build/vite/index.js +9 -7
  25. package/dist/cmd/build/vite/index.js.map +1 -1
  26. package/dist/cmd/build/vite/lifecycle-generator.d.ts +1 -1
  27. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/lifecycle-generator.js +19 -5
  29. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  30. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  31. package/dist/cmd/build/vite/metadata-generator.js +145 -0
  32. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  33. package/dist/cmd/build/vite/registry-generator.d.ts +3 -3
  34. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  35. package/dist/cmd/build/vite/registry-generator.js +627 -103
  36. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  37. package/dist/cmd/build/vite/route-discovery.d.ts +4 -0
  38. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  39. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  40. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  41. package/dist/cmd/build/vite/server-bundler.js +18 -1
  42. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  43. package/dist/cmd/build/vite/vite-builder.d.ts +1 -1
  44. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/vite-builder.js +28 -18
  46. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  47. package/dist/cmd/build/vite-bundler.js +6 -6
  48. package/dist/cmd/build/vite-bundler.js.map +1 -1
  49. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  50. package/dist/cmd/cloud/deploy.js +11 -5
  51. package/dist/cmd/cloud/deploy.js.map +1 -1
  52. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  53. package/dist/cmd/dev/file-watcher.js +4 -2
  54. package/dist/cmd/dev/file-watcher.js.map +1 -1
  55. package/dist/cmd/dev/index.d.ts.map +1 -1
  56. package/dist/cmd/dev/index.js +102 -21
  57. package/dist/cmd/dev/index.js.map +1 -1
  58. package/dist/cmd/dev/sync.d.ts.map +1 -1
  59. package/dist/cmd/dev/sync.js +19 -3
  60. package/dist/cmd/dev/sync.js.map +1 -1
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +8 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +0 -1
  67. package/dist/index.js.map +1 -1
  68. package/package.json +5 -8
  69. package/src/api.ts +1 -1
  70. package/src/cmd/build/ast.ts +161 -48
  71. package/src/cmd/build/entry-generator.ts +225 -190
  72. package/src/cmd/build/vite/agent-discovery.ts +151 -58
  73. package/src/cmd/build/vite/bun-dev-server.ts +1 -1
  74. package/src/cmd/build/vite/docs-generator.ts +87 -0
  75. package/src/cmd/build/vite/index.ts +9 -7
  76. package/src/cmd/build/vite/lifecycle-generator.ts +19 -5
  77. package/src/cmd/build/vite/metadata-generator.ts +178 -0
  78. package/src/cmd/build/vite/registry-generator.ts +727 -108
  79. package/src/cmd/build/vite/route-discovery.ts +4 -0
  80. package/src/cmd/build/vite/server-bundler.ts +20 -1
  81. package/src/cmd/build/vite/vite-builder.ts +44 -30
  82. package/src/cmd/build/vite-bundler.ts +6 -6
  83. package/src/cmd/cloud/deploy.ts +15 -5
  84. package/src/cmd/dev/file-watcher.ts +8 -2
  85. package/src/cmd/dev/index.ts +141 -30
  86. package/src/cmd/dev/sync.ts +41 -6
  87. package/src/config.ts +9 -0
  88. package/src/index.ts +0 -5
  89. package/src/runtime-bootstrap.md +1 -1
  90. package/dist/runtime-bootstrap.d.ts +0 -56
  91. package/dist/runtime-bootstrap.d.ts.map +0 -1
  92. package/dist/runtime-bootstrap.js +0 -95
  93. package/dist/runtime-bootstrap.js.map +0 -1
  94. package/src/runtime-bootstrap.ts +0 -131
@@ -33,8 +33,12 @@ export interface RouteInfo {
33
33
  routeType: 'api' | 'sms' | 'email' | 'cron' | 'websocket' | 'sse' | 'stream';
34
34
  agentVariable?: string;
35
35
  agentImportPath?: string;
36
+ agentName?: string;
37
+ agentDescription?: string;
36
38
  inputSchemaVariable?: string;
37
39
  outputSchemaVariable?: string;
40
+ inputSchemaCode?: string;
41
+ outputSchemaCode?: string;
38
42
  stream?: boolean;
39
43
  }
40
44
 
@@ -24,7 +24,7 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
24
24
 
25
25
  logger.debug('[server-bundler] Starting server bundle process');
26
26
 
27
- const entryPath = join(rootDir, '.agentuity/app.generated.ts');
27
+ const entryPath = join(rootDir, 'src/generated/app.ts');
28
28
  const outDir = join(rootDir, '.agentuity');
29
29
 
30
30
  logger.debug(`[server-bundler] Entry: ${entryPath}, OutDir: ${outDir}`);
@@ -246,6 +246,10 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
246
246
  minify: !dev,
247
247
  sourcemap: (dev ? 'inline' : 'external') as 'inline' | 'external',
248
248
  external,
249
+ // CRITICAL: Disable environment variable inlining for server builds
250
+ // Server code must read process.env at RUNTIME, not have values baked in at build time
251
+ // Without this, NODE_ENV and other env vars get inlined as string literals
252
+ env: 'disable' as const,
249
253
  define: userDefine, // Include custom define values from agentuity.config.ts
250
254
  plugins: [patchPlugin],
251
255
  naming: {
@@ -257,6 +261,12 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
257
261
  `Bun.build config: ${JSON.stringify({ ...buildConfig, external: `[${external.length} packages]` }, null, 2)}`
258
262
  );
259
263
 
264
+ // WORKAROUND: Temporarily delete NODE_ENV to prevent Bun.build from inlining it
265
+ // See: https://github.com/oven-sh/bun/issues/20183
266
+ // Even with env: 'disable', Bun.build still inlines NODE_ENV at build time
267
+ const originalNodeEnv = process.env.NODE_ENV;
268
+ delete process.env.NODE_ENV;
269
+
260
270
  // Verify entry point exists before building
261
271
  if (!(await Bun.file(entryPath).exists())) {
262
272
  throw new Error(`Entry point not found: ${entryPath}`);
@@ -268,6 +278,10 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
268
278
  try {
269
279
  result = await Bun.build(buildConfig);
270
280
  } catch (error: unknown) {
281
+ // Restore NODE_ENV after build attempt
282
+ if (originalNodeEnv !== undefined) {
283
+ process.env.NODE_ENV = originalNodeEnv;
284
+ }
271
285
  logger.error('Bun.build threw an exception');
272
286
 
273
287
  // Handle AggregateError with build/resolve messages
@@ -286,6 +300,11 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
286
300
  throw error;
287
301
  }
288
302
 
303
+ // Restore NODE_ENV after successful build
304
+ if (originalNodeEnv !== undefined) {
305
+ process.env.NODE_ENV = originalNodeEnv;
306
+ }
307
+
289
308
  if (!result.success) {
290
309
  logger.error('Bun.build failed for server');
291
310
  logger.error(
@@ -25,7 +25,7 @@ export interface ViteBuildOptions {
25
25
 
26
26
  /**
27
27
  * Run a Vite build for the specified mode
28
- * Generates vite config in .agentuity/.vite/ and uses it
28
+ * Uses inline Vite config (customizable via agentuity.config.ts)
29
29
  */
30
30
  export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
31
31
  const { rootDir, mode, dev = false, projectId = '', deploymentId = '', logger } = options;
@@ -35,7 +35,17 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
35
35
  // For server mode, use Bun.build (preserves process.env at runtime)
36
36
  if (mode === 'server') {
37
37
  try {
38
- // First, generate the entry file
38
+ const srcDir = join(rootDir, 'src');
39
+
40
+ // Generate documentation files (if they don't exist)
41
+ const { generateDocumentation } = await import('./docs-generator');
42
+ await generateDocumentation(srcDir, logger);
43
+
44
+ // Generate lifecycle types (if setup() exists)
45
+ const { generateLifecycleTypes } = await import('./lifecycle-generator');
46
+ await generateLifecycleTypes(rootDir, srcDir, logger);
47
+
48
+ // Then, generate the entry file
39
49
  const { generateEntryFile } = await import('../entry-generator');
40
50
  await generateEntryFile({
41
51
  rootDir,
@@ -45,7 +55,7 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
45
55
  mode: dev ? 'dev' : 'prod',
46
56
  });
47
57
 
48
- // Then, build with Bun.build
58
+ // Finally, build with Bun.build
49
59
  const { installExternalsAndBuild } = await import('./server-bundler');
50
60
  await installExternalsAndBuild({
51
61
  rootDir,
@@ -213,10 +223,36 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
213
223
  await generateWorkbenchFiles(rootDir, projectId, workbenchConfig, logger);
214
224
  }
215
225
 
226
+ // 1. Discover agents and routes BEFORE builds
227
+ logger.debug('Discovering agents and routes...');
228
+ const { generateAgentRegistry, generateRouteRegistry } = await import('./registry-generator');
229
+ const { discoverAgents } = await import('./agent-discovery');
230
+ const { discoverRoutes } = await import('./route-discovery');
231
+
232
+ const srcDir = join(rootDir, 'src');
233
+ const agentMetadata = await discoverAgents(
234
+ srcDir,
235
+ projectId,
236
+ options.deploymentId || '',
237
+ logger
238
+ );
239
+ const { routes, routeInfoList } = await discoverRoutes(
240
+ srcDir,
241
+ projectId,
242
+ options.deploymentId || '',
243
+ logger
244
+ );
245
+
246
+ // Generate agent and route registries for type augmentation BEFORE builds
247
+ // (TypeScript needs these files to exist during type checking)
248
+ generateAgentRegistry(srcDir, agentMetadata);
249
+ generateRouteRegistry(srcDir, routeInfoList);
250
+ logger.debug('Agent and route registries generated');
251
+
216
252
  // Check if web frontend exists
217
253
  const hasWebFrontend = await Bun.file(join(rootDir, 'src', 'web', 'index.html')).exists();
218
254
 
219
- // 1. Build client (only if web frontend exists)
255
+ // 2. Build client (only if web frontend exists)
220
256
  if (hasWebFrontend) {
221
257
  logger.debug('Building client assets...');
222
258
  try {
@@ -237,7 +273,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
237
273
  logger.debug('Skipping client build - no src/web/index.html found');
238
274
  }
239
275
 
240
- // 2. Build workbench (if enabled in config)
276
+ // 3. Build workbench (if enabled in config)
241
277
  if (workbenchConfig.enabled) {
242
278
  logger.debug('Building workbench assets...');
243
279
  try {
@@ -256,7 +292,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
256
292
  }
257
293
  }
258
294
 
259
- // 3. Build server
295
+ // 4. Build server
260
296
  logger.debug('Building server...');
261
297
  try {
262
298
  const started = Date.now();
@@ -268,31 +304,9 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
268
304
  throw error;
269
305
  }
270
306
 
271
- // 4. Generate registry and metadata (after all builds complete)
272
- logger.debug('Generating agent registry and metadata...');
307
+ // 5. Generate metadata (after all builds complete)
308
+ logger.debug('Generating metadata...');
273
309
  const { generateMetadata, writeMetadataFile } = await import('./metadata-generator');
274
- const { generateAgentRegistry, generateRouteRegistry } = await import('./registry-generator');
275
- const { discoverAgents } = await import('./agent-discovery');
276
- const { discoverRoutes } = await import('./route-discovery');
277
-
278
- const srcDir = join(rootDir, 'src');
279
- const agentMetadata = await discoverAgents(
280
- srcDir,
281
- projectId,
282
- options.deploymentId || '',
283
- logger
284
- );
285
- const { routes, routeInfoList } = await discoverRoutes(
286
- srcDir,
287
- projectId,
288
- options.deploymentId || '',
289
- logger
290
- );
291
-
292
- // Generate agent and route registries for type augmentation
293
- generateAgentRegistry(srcDir, agentMetadata);
294
- generateRouteRegistry(srcDir, routeInfoList);
295
- logger.debug('Agent and route registries generated');
296
310
 
297
311
  // Generate metadata
298
312
  const metadata = await generateMetadata({
@@ -75,8 +75,8 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
75
75
  }
76
76
 
77
77
  try {
78
- // Run all Vite builds (client -> workbench -> server)
79
- logger.debug('Starting Vite builds...');
78
+ // Run all builds (client -> workbench -> server)
79
+ logger.debug('Starting builds...');
80
80
 
81
81
  const result = await runAllBuilds({
82
82
  rootDir,
@@ -90,16 +90,16 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
90
90
  });
91
91
 
92
92
  if (result.client.included) {
93
- output.push(tui.muted(`✓ Client Built in ${result.client.duration}ms`));
93
+ output.push(tui.muted(`✓ Client built in ${result.client.duration}ms`));
94
94
  }
95
95
  if (result.workbench.included) {
96
- output.push(tui.muted(`✓ Workbench Built in ${result.workbench.duration}ms`));
96
+ output.push(tui.muted(`✓ Workbench built in ${result.workbench.duration}ms`));
97
97
  }
98
98
  if (result.server.included) {
99
- output.push(tui.muted(`✓ Server Built in ${result.server.duration}ms`));
99
+ output.push(tui.muted(`✓ Server built in ${result.server.duration}ms`));
100
100
  }
101
101
 
102
- logger.debug('Vite builds complete');
102
+ logger.debug('All builds complete');
103
103
 
104
104
  return { output };
105
105
  } catch (error) {
@@ -6,8 +6,9 @@ import { tmpdir } from 'node:os';
6
6
  import { StructuredError } from '@agentuity/core';
7
7
  import { isRunningFromExecutable } from '../upgrade';
8
8
  import { createSubcommand } from '../../types';
9
+ import { getUserAgent } from '../../api';
9
10
  import * as tui from '../../tui';
10
- import { saveProjectDir, getDefaultConfigDir } from '../../config';
11
+ import { saveProjectDir, getDefaultConfigDir, loadProjectSDKKey } from '../../config';
11
12
  import {
12
13
  runSteps,
13
14
  stepSuccess,
@@ -106,6 +107,15 @@ export const deploySubcommand = createSubcommand({
106
107
  let statusResult: DeploymentStatusResult | undefined;
107
108
  const logs: string[] = [];
108
109
 
110
+ const sdkKey = await loadProjectSDKKey(ctx.logger, ctx.projectDir);
111
+
112
+ // Ensure SDK key is present before proceeding
113
+ if (!sdkKey) {
114
+ ctx.logger.fatal(
115
+ 'SDK key not found. Run "agentuity auth login" to authenticate or set AGENTUITY_SDK_KEY environment variable.'
116
+ );
117
+ }
118
+
109
119
  try {
110
120
  await saveProjectDir(projectDir);
111
121
 
@@ -241,10 +251,6 @@ export const deploySubcommand = createSubcommand({
241
251
  const deploymentZip = join(tmpdir(), `${deployment.id}.zip`);
242
252
  await zipDir(join(projectDir, '.agentuity'), deploymentZip, {
243
253
  filter: (_filename: string, relative: string) => {
244
- // Exclude Vite-specific build artifacts
245
- if (relative.endsWith('.generated.ts')) {
246
- return false;
247
- }
248
254
  if (relative.startsWith('.vite/')) {
249
255
  return false;
250
256
  }
@@ -444,6 +450,10 @@ export const deploySubcommand = createSubcommand({
444
450
  logger.debug('fetching stream: %s/%s', streamsUrl, streamId);
445
451
  const resp = await fetch(`${streamsUrl}/${streamId}`, {
446
452
  signal: logStreamController.signal,
453
+ headers: {
454
+ Authorization: `Bearer ${sdkKey}`,
455
+ 'User-Agent': getUserAgent(),
456
+ },
447
457
  });
448
458
  if (!resp.ok || !resp.body) {
449
459
  ctx.logger.trace(
@@ -142,10 +142,16 @@ export function createFileWatcher(options: FileWatcherOptions): FileWatcherManag
142
142
  const contents = readdirSync(absPath);
143
143
  if (contents.length === 0) {
144
144
  // Check if this is an agent or API directory
145
- if (normalizedPath.startsWith('src/agent/') || normalizedPath.includes('/src/agent/')) {
145
+ if (
146
+ normalizedPath.startsWith('src/agent/') ||
147
+ normalizedPath.includes('/src/agent/')
148
+ ) {
146
149
  logger.debug('Agent directory created: %s', changedFile);
147
150
  createAgentTemplates(absPath);
148
- } else if (normalizedPath.startsWith('src/api/') || normalizedPath.includes('/src/api/')) {
151
+ } else if (
152
+ normalizedPath.startsWith('src/api/') ||
153
+ normalizedPath.includes('/src/api/')
154
+ ) {
149
155
  logger.debug('API directory created: %s', changedFile);
150
156
  createAPITemplates(absPath);
151
157
  }
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { resolve, join } from 'node:path';
3
3
  import { existsSync } from 'node:fs';
4
- import { internalExit } from '@agentuity/runtime';
4
+ import { getServiceUrls } from '@agentuity/server';
5
5
  import { createCommand } from '../../types';
6
6
  import { startBunDevServer } from '../build/vite/bun-dev-server';
7
7
  import { startViteAssetServer } from '../build/vite/vite-asset-server';
@@ -99,6 +99,13 @@ export const command = createCommand({
99
99
 
100
100
  const interactive = !shouldDisableInteractive(opts.interactive);
101
101
 
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ let originalExit = (globalThis as any).AGENTUITY_PROCESS_EXIT;
104
+
105
+ if (!originalExit) {
106
+ originalExit = process.exit.bind(process);
107
+ }
108
+
102
109
  for (const filename of mustHaves) {
103
110
  if (!existsSync(filename)) {
104
111
  missing.push(filename);
@@ -110,17 +117,22 @@ export const command = createCommand({
110
117
  for (const filename of missing) {
111
118
  tui.bullet(`Missing ${filename}`);
112
119
  }
113
- internalExit(1);
120
+ originalExit(1);
114
121
  }
115
122
 
116
123
  // Setup devmode and gravity (if using public URL)
117
124
  const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
118
125
  const apiClient = auth ? new APIClient(getAPIBaseURL(config), logger, config) : null;
119
- createDevmodeSyncService({
120
- logger,
121
- apiClient,
122
- mock: useMockService,
123
- });
126
+ const syncService = apiClient
127
+ ? createDevmodeSyncService({
128
+ logger,
129
+ apiClient,
130
+ mock: useMockService,
131
+ })
132
+ : null;
133
+
134
+ // Track previous metadata for sync diffing
135
+ let previousMetadata: Awaited<ReturnType<typeof import('../build/vite/metadata-generator').generateMetadata>> | undefined;
124
136
 
125
137
  let devmode: DevmodeResponse | undefined;
126
138
  let gravityBin: string | undefined;
@@ -224,8 +236,8 @@ export const command = createCommand({
224
236
 
225
237
  // Start Vite asset server ONCE before restart loop
226
238
  // Vite handles frontend HMR independently and stays running across backend restarts
227
- let vitePort: number;
228
239
  let viteServer: ServerLike | null = null;
240
+ let vitePort: number;
229
241
 
230
242
  try {
231
243
  logger.debug('Starting Vite asset server...');
@@ -241,13 +253,15 @@ export const command = createCommand({
241
253
  );
242
254
  } catch (error) {
243
255
  tui.error(`Failed to start Vite asset server: ${error}`);
244
- internalExit(1);
256
+ originalExit(1);
257
+ return;
245
258
  }
246
259
 
247
260
  // Restart loop - allows BACKEND server to restart on file changes
248
261
  // Vite stays running and handles frontend changes via HMR
249
262
  let shouldRestart = false;
250
263
  let gravityProcess: ProcessLike | null = null;
264
+ let stdinListenerRegistered = false; // Track if stdin listener is already registered
251
265
 
252
266
  const restartServer = () => {
253
267
  shouldRestart = true;
@@ -293,11 +307,22 @@ export const command = createCommand({
293
307
  }
294
308
  }
295
309
 
296
- internalExit(0);
310
+ originalExit(0);
297
311
  };
298
312
 
299
- process.on('SIGINT', cleanup);
300
- process.on('SIGTERM', cleanup);
313
+ // SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
314
+ let devShutdownHandled = false;
315
+ process.on('SIGINT', async () => {
316
+ if (devShutdownHandled) return;
317
+ devShutdownHandled = true;
318
+ // The bundle handles its own shutdown, we clean up dev resources
319
+ await cleanup();
320
+ });
321
+ process.on('SIGTERM', async () => {
322
+ if (devShutdownHandled) return;
323
+ devShutdownHandled = true;
324
+ await cleanup();
325
+ });
301
326
 
302
327
  // Ensure Vite and gravity are always killed on exit (even if cleanup is bypassed)
303
328
  process.on('exit', () => {
@@ -329,23 +354,76 @@ export const command = createCommand({
329
354
  fileWatcher.pause();
330
355
 
331
356
  try {
332
- // Generate entry file for Vite before starting dev server
357
+ // Generate entry file and bundle for dev server (with LLM patches)
333
358
  await tui.spinner({
334
- message: 'Generating entry file',
359
+ message: 'Building dev bundle',
335
360
  callback: async () => {
336
- const { generateEntryFile } = await import('../build/entry-generator');
337
- await generateEntryFile({
338
- rootDir,
339
- projectId: project?.projectId ?? '',
340
- deploymentId,
341
- logger,
342
- mode: 'dev',
343
- });
361
+ const { generateEntryFile } = await import('../build/entry-generator');
362
+ await generateEntryFile({
363
+ rootDir,
364
+ projectId: project?.projectId ?? '',
365
+ deploymentId,
366
+ logger,
367
+ mode: 'dev',
368
+ });
369
+
370
+ // Bundle the app with LLM patches (dev mode = no minification)
371
+ const { installExternalsAndBuild } = await import('../build/vite/server-bundler');
372
+ await installExternalsAndBuild({
373
+ rootDir,
374
+ dev: true, // DevMode: no minification, inline sourcemaps
375
+ logger,
376
+ });
377
+
378
+ // Generate metadata file (needed for eval ID lookup at runtime)
379
+ const { discoverAgents } = await import('../build/vite/agent-discovery');
380
+ const { discoverRoutes } = await import('../build/vite/route-discovery');
381
+ const { generateMetadata, writeMetadataFile } = await import(
382
+ '../build/vite/metadata-generator'
383
+ );
384
+
385
+ const srcDir = join(rootDir, 'src');
386
+ const agents = await discoverAgents(
387
+ srcDir,
388
+ project?.projectId ?? '',
389
+ deploymentId,
390
+ logger
391
+ );
392
+ const { routes } = await discoverRoutes(
393
+ srcDir,
394
+ project?.projectId ?? '',
395
+ deploymentId,
396
+ logger
397
+ );
398
+
399
+ const metadata = await generateMetadata({
400
+ rootDir,
401
+ projectId: project?.projectId ?? '',
402
+ orgId: project?.orgId ?? '',
403
+ deploymentId,
404
+ agents,
405
+ routes,
406
+ dev: true,
407
+ logger,
408
+ });
409
+
410
+ writeMetadataFile(rootDir, metadata, true, logger);
411
+
412
+ // Sync metadata with backend (creates agents and evals in the database)
413
+ if (syncService && project?.projectId) {
414
+ await syncService.sync(
415
+ metadata,
416
+ previousMetadata,
417
+ project.projectId,
418
+ deploymentId
419
+ );
420
+ previousMetadata = metadata;
421
+ }
344
422
  },
345
423
  clearOnSuccess: true,
346
424
  });
347
425
  } catch (error) {
348
- tui.error(`Failed to generate entry file: ${error}`);
426
+ tui.error(`Failed to build dev bundle: ${error}`);
349
427
  tui.warn('Waiting for file changes to retry...');
350
428
 
351
429
  // Resume watcher to detect changes for retry
@@ -364,6 +442,34 @@ export const command = createCommand({
364
442
  }
365
443
 
366
444
  try {
445
+ // Set environment variables for LLM provider patches BEFORE starting server
446
+ // These must be set so the bundled patches can route LLM calls through AI Gateway
447
+ const serviceUrls = getServiceUrls(project?.region);
448
+
449
+ process.env.AGENTUITY_SDK_DEV_MODE = 'true';
450
+ process.env.AGENTUITY_ENV = 'development';
451
+ process.env.NODE_ENV = 'development';
452
+ if (project?.region) {
453
+ process.env.AGENTUITY_REGION = project.region;
454
+ }
455
+ process.env.PORT = String(opts.port);
456
+ process.env.AGENTUITY_PORT = process.env.PORT;
457
+
458
+ if (project) {
459
+ process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
460
+ process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
461
+ process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
462
+ process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
463
+ process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
464
+ process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
465
+ process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
466
+ }
467
+
468
+ // Set Vite port for asset proxying in bundled app
469
+ process.env.VITE_PORT = String(vitePort);
470
+
471
+ logger.debug('Set VITE_PORT=%s for asset proxying', process.env.VITE_PORT);
472
+
367
473
  // Start Bun dev server (Vite already running, just start backend)
368
474
  await startBunDevServer({
369
475
  rootDir,
@@ -375,8 +481,6 @@ export const command = createCommand({
375
481
  vitePort, // Pass port of already-running Vite server
376
482
  });
377
483
 
378
- // Note: Bun server runs in-process, no separate app process needed
379
-
380
484
  // Wait for app.ts to finish loading (Vite is ready but app may still be initializing)
381
485
  // Give it 2 seconds to ensure app initialization completes
382
486
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -454,8 +558,14 @@ export const command = createCommand({
454
558
  // TODO: Integrate sync service with Vite's buildStart/buildEnd hooks
455
559
  // The sync service will be called when metadata changes are detected
456
560
 
457
- // Handle keyboard shortcuts
458
- if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
561
+ // Handle keyboard shortcuts - only register listener once
562
+ if (
563
+ interactive &&
564
+ process.stdin.isTTY &&
565
+ process.stdout.isTTY &&
566
+ !stdinListenerRegistered
567
+ ) {
568
+ stdinListenerRegistered = true;
459
569
  process.stdin.setRawMode(true);
460
570
  process.stdin.resume();
461
571
  process.stdin.setEncoding('utf8');
@@ -470,9 +580,10 @@ export const command = createCommand({
470
580
  process.stdin.on('data', (data) => {
471
581
  const key = data.toString();
472
582
 
473
- // Handle Ctrl+C
583
+ // Handle Ctrl+C - send SIGINT to trigger graceful shutdown
474
584
  if (key === '\u0003') {
475
- internalExit(0);
585
+ process.kill(process.pid, 'SIGINT');
586
+ return;
476
587
  }
477
588
 
478
589
  switch (key) {
@@ -489,7 +600,7 @@ export const command = createCommand({
489
600
  });
490
601
  break;
491
602
  case 'q':
492
- internalExit(0);
603
+ originalExit(0);
493
604
  break;
494
605
  default:
495
606
  process.stdout.write(data);
@@ -171,9 +171,21 @@ class DevmodeSyncService implements IDevmodeSyncService {
171
171
  for (const agent of currentMetadata.agents || []) {
172
172
  if (agent.evals) {
173
173
  currentEvalCount += agent.evals.length;
174
+ this.logger.info(
175
+ '[CLI EVAL SYNC] Agent "%s" has %d eval(s)',
176
+ agent.name,
177
+ agent.evals.length
178
+ );
179
+ for (const evalItem of agent.evals) {
180
+ this.logger.info(
181
+ '[CLI EVAL SYNC] - %s (evalId: %s)',
182
+ evalItem.name,
183
+ evalItem.evalId
184
+ );
185
+ }
174
186
  }
175
187
  }
176
- this.logger.debug('Processing %d current eval(s)', currentEvalCount);
188
+ this.logger.info('[CLI EVAL SYNC] Total current eval(s): %d', currentEvalCount);
177
189
 
178
190
  // Get agents and evals to sync using shared diff logic
179
191
  const { create: agentsToCreate, delete: agentsToDelete } = getAgentsToSync(
@@ -261,7 +273,14 @@ class DevmodeSyncService implements IDevmodeSyncService {
261
273
  evalsToDelete: string[],
262
274
  deploymentId: string
263
275
  ): Promise<void> {
276
+ this.logger.info(
277
+ '[CLI EVAL SYNC] syncEvals called: %d to create, %d to delete',
278
+ evals.length,
279
+ evalsToDelete.length
280
+ );
281
+
264
282
  if (evals.length === 0 && evalsToDelete.length === 0) {
283
+ this.logger.info('[CLI EVAL SYNC] No evals to sync, skipping');
265
284
  return;
266
285
  }
267
286
 
@@ -270,12 +289,28 @@ class DevmodeSyncService implements IDevmodeSyncService {
270
289
  create: evals,
271
290
  delete: evalsToDelete,
272
291
  };
273
- this.logger.trace(
274
- '[CLI EVAL SYNC] Sending payload to POST /cli/devmode/eval: %s',
275
- JSON.stringify(payload, null, 2)
276
- );
277
292
 
278
- await this.apiClient.post('/cli/devmode/eval', payload, z.object({ success: z.boolean() }));
293
+ this.logger.info('[CLI EVAL SYNC] Sending payload to POST /cli/devmode/eval:');
294
+ for (const evalItem of evals) {
295
+ this.logger.info(
296
+ '[CLI EVAL SYNC] - %s (id: %s, evalId: %s)',
297
+ evalItem.name,
298
+ evalItem.id,
299
+ evalItem.evalId
300
+ );
301
+ }
302
+
303
+ try {
304
+ await this.apiClient.post(
305
+ '/cli/devmode/eval',
306
+ payload,
307
+ z.object({ success: z.boolean() })
308
+ );
309
+ this.logger.info('[CLI EVAL SYNC] Sync successful');
310
+ } catch (error) {
311
+ this.logger.error('[CLI EVAL SYNC] Sync failed: %s', error);
312
+ throw error;
313
+ }
279
314
  }
280
315
  }
281
316
 
package/src/config.ts CHANGED
@@ -364,6 +364,15 @@ export async function getAuth(): Promise<AuthData | null> {
364
364
  };
365
365
  }
366
366
 
367
+ // Priority 1a: Allow automated login from environment variables (this is set in deployment)
368
+ if (process.env.AGENTUITY_API_KEY) {
369
+ return {
370
+ apiKey: process.env.AGENTUITY_API_KEY,
371
+ userId: '',
372
+ expires: new Date(Date.now() + 30 * 60_000),
373
+ };
374
+ }
375
+
367
376
  // Priority 2: On macOS, try to read from Keychain
368
377
  if (isMacOS()) {
369
378
  try {
package/src/index.ts CHANGED
@@ -75,11 +75,6 @@ export { showBanner } from './banner';
75
75
  export { discoverCommands } from './cmd';
76
76
  export { detectColorScheme } from './terminal';
77
77
  export { getCommandPrefix, getCommand } from './command-prefix';
78
- export {
79
- bootstrapRuntimeEnv,
80
- type RuntimeBootstrapOptions,
81
- type RuntimeBootstrapResult,
82
- } from './runtime-bootstrap';
83
78
  export * as tui from './tui';
84
79
  export {
85
80
  createRepl,
@@ -43,7 +43,7 @@ Call `bootstrapRuntimeEnv()` at the top of your `app.ts` **before** `createApp()
43
43
 
44
44
  ```ts
45
45
  import { createApp } from '@agentuity/runtime';
46
- import { bootstrapRuntimeEnv } from '@agentuity/cli';
46
+ import { bootstrapRuntimeEnv } from '@agentuity/runtime';
47
47
 
48
48
  // Bootstrap runtime environment based on active profile
49
49
  await bootstrapRuntimeEnv();