@agentuity/cli 0.0.103 → 0.0.105

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 (52) hide show
  1. package/bin/cli.ts +6 -3
  2. package/dist/cmd/ai/prompt/version.d.ts +1 -0
  3. package/dist/cmd/ai/prompt/version.d.ts.map +1 -1
  4. package/dist/cmd/ai/prompt/version.js +3 -2
  5. package/dist/cmd/ai/prompt/version.js.map +1 -1
  6. package/dist/cmd/build/ast.d.ts.map +1 -1
  7. package/dist/cmd/build/ast.js +50 -34
  8. package/dist/cmd/build/ast.js.map +1 -1
  9. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  10. package/dist/cmd/build/entry-generator.js +21 -17
  11. package/dist/cmd/build/entry-generator.js.map +1 -1
  12. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  13. package/dist/cmd/build/vite/agent-discovery.js +41 -2
  14. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  15. package/dist/cmd/build/vite/server-bundler.js +1 -1
  16. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  17. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/vite-asset-server.js +53 -4
  19. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  20. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/vite-builder.js +3 -0
  22. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  23. package/dist/cmd/dev/dev-lock.d.ts +62 -0
  24. package/dist/cmd/dev/dev-lock.d.ts.map +1 -0
  25. package/dist/cmd/dev/dev-lock.js +250 -0
  26. package/dist/cmd/dev/dev-lock.js.map +1 -0
  27. package/dist/cmd/dev/index.d.ts.map +1 -1
  28. package/dist/cmd/dev/index.js +69 -12
  29. package/dist/cmd/dev/index.js.map +1 -1
  30. package/dist/cmd/dev/sync.js +6 -6
  31. package/dist/cmd/dev/sync.js.map +1 -1
  32. package/dist/cmd/project/download.d.ts +1 -0
  33. package/dist/cmd/project/download.d.ts.map +1 -1
  34. package/dist/cmd/project/download.js +7 -5
  35. package/dist/cmd/project/download.js.map +1 -1
  36. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  37. package/dist/cmd/project/template-flow.js +3 -1
  38. package/dist/cmd/project/template-flow.js.map +1 -1
  39. package/package.json +4 -4
  40. package/src/cmd/ai/prompt/api.md +26 -21
  41. package/src/cmd/ai/prompt/version.ts +3 -2
  42. package/src/cmd/build/ast.ts +52 -34
  43. package/src/cmd/build/entry-generator.ts +21 -18
  44. package/src/cmd/build/vite/agent-discovery.ts +45 -3
  45. package/src/cmd/build/vite/server-bundler.ts +1 -1
  46. package/src/cmd/build/vite/vite-asset-server.ts +56 -4
  47. package/src/cmd/build/vite/vite-builder.ts +4 -1
  48. package/src/cmd/dev/dev-lock.ts +332 -0
  49. package/src/cmd/dev/index.ts +79 -12
  50. package/src/cmd/dev/sync.ts +6 -6
  51. package/src/cmd/project/download.ts +8 -6
  52. package/src/cmd/project/template-flow.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentuity/cli",
3
- "version": "0.0.103",
3
+ "version": "0.0.105",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Agentuity employees and contributors",
6
6
  "type": "module",
@@ -40,8 +40,8 @@
40
40
  "prepublishOnly": "bun run clean && bun run build"
41
41
  },
42
42
  "dependencies": {
43
- "@agentuity/core": "0.0.103",
44
- "@agentuity/server": "0.0.103",
43
+ "@agentuity/core": "0.0.105",
44
+ "@agentuity/server": "0.0.105",
45
45
  "@datasert/cronjs-parser": "^1.4.0",
46
46
  "@terascope/fetch-github-release": "^2.2.1",
47
47
  "@vitejs/plugin-react": "^5.1.2",
@@ -61,7 +61,7 @@
61
61
  "zod": "^4.1.12"
62
62
  },
63
63
  "devDependencies": {
64
- "@agentuity/test-utils": "0.0.103",
64
+ "@agentuity/test-utils": "0.0.105",
65
65
  "@types/adm-zip": "^0.5.7",
66
66
  "@types/bun": "latest",
67
67
  "@types/tar-fs": "^2.0.4",
@@ -311,40 +311,45 @@ return c.json({ data: 'value' }, 200, {
311
311
  ## Streaming Routes
312
312
 
313
313
  ```typescript
314
- import { createRouter } from '@agentuity/runtime';
314
+ import { createRouter, stream, sse, websocket } from '@agentuity/runtime';
315
315
 
316
316
  const router = createRouter();
317
317
 
318
- // Stream response
319
- router.stream('/events', (c) => {
320
- return new ReadableStream({
321
- start(controller) {
322
- controller.enqueue('event 1\n');
323
- controller.enqueue('event 2\n');
324
- controller.close();
325
- },
326
- });
327
- });
318
+ // Stream response (use with POST)
319
+ router.post(
320
+ '/events',
321
+ stream((c) => {
322
+ return new ReadableStream({
323
+ start(controller) {
324
+ controller.enqueue('event 1\n');
325
+ controller.enqueue('event 2\n');
326
+ controller.close();
327
+ },
328
+ });
329
+ })
330
+ );
328
331
 
329
- // Server-Sent Events
330
- router.sse('/notifications', (c) => {
331
- return (stream) => {
332
+ // Server-Sent Events (use with GET)
333
+ router.get(
334
+ '/notifications',
335
+ sse((c, stream) => {
332
336
  stream.writeSSE({ data: 'Hello', event: 'message' });
333
337
  stream.writeSSE({ data: 'World', event: 'message' });
334
- };
335
- });
338
+ })
339
+ );
336
340
 
337
- // WebSocket
338
- router.websocket('/ws', (c) => {
339
- return (ws) => {
341
+ // WebSocket (use with GET)
342
+ router.get(
343
+ '/ws',
344
+ websocket((c, ws) => {
340
345
  ws.onOpen(() => {
341
346
  ws.send('Connected!');
342
347
  });
343
348
  ws.onMessage((event) => {
344
349
  ws.send(`Echo: ${event.data}`);
345
350
  });
346
- };
347
- });
351
+ })
352
+ );
348
353
 
349
354
  export default router;
350
355
  ```
@@ -8,7 +8,7 @@
8
8
  * This allows detecting if the source template has changed.
9
9
  */
10
10
 
11
- const HASH_REGEX = /\n?<!-- prompt_hash: ([a-f0-9]+) -->$/;
11
+ const HASH_REGEX = /\n?<!-- prompt_hash: ([a-f0-9]+) -->\n?$/;
12
12
 
13
13
  /**
14
14
  * Compute SHA256 hash of content using Bun's built-in hasher.
@@ -37,10 +37,11 @@ export function extractHash(content: string): string | null {
37
37
 
38
38
  /**
39
39
  * Generate content with hash comment appended.
40
+ * Ensures the output ends with a newline for POSIX compliance and Prettier compatibility.
40
41
  */
41
42
  export function appendHashComment(content: string): string {
42
43
  const hash = computeHash(content);
43
- return `${content}\n<!-- prompt_hash: ${hash} -->`;
44
+ return `${content}\n<!-- prompt_hash: ${hash} -->\n`;
44
45
  }
45
46
 
46
47
  /**
@@ -1315,6 +1315,52 @@ export async function parseRoute(
1315
1315
  case 'delete': {
1316
1316
  if (action && (action as ASTLiteral).type === 'Literal') {
1317
1317
  suffix = String((action as ASTLiteral).value);
1318
+
1319
+ // Check if any argument is a middleware function call (websocket, sse, stream, cron)
1320
+ // New pattern: router.get('/ws', websocket((c, ws) => { ... }))
1321
+ for (const arg of statement.expression.arguments) {
1322
+ if ((arg as ASTCallExpression).type === 'CallExpression') {
1323
+ const callExpr = arg as ASTCallExpression;
1324
+ // Only handle simple Identifier callees (e.g., websocket(), sse())
1325
+ // Skip MemberExpression callees (e.g., obj.method())
1326
+ if (callExpr.callee.type !== 'Identifier') {
1327
+ continue;
1328
+ }
1329
+ const calleeName = (callExpr.callee as ASTNodeIdentifier).name;
1330
+ if (
1331
+ calleeName === 'websocket' ||
1332
+ calleeName === 'sse' ||
1333
+ calleeName === 'stream'
1334
+ ) {
1335
+ type = calleeName;
1336
+ break;
1337
+ }
1338
+ if (calleeName === 'cron') {
1339
+ type = 'cron';
1340
+ // First argument to cron() is the schedule expression
1341
+ if (callExpr.arguments && callExpr.arguments.length > 0) {
1342
+ const cronArg = callExpr.arguments[0] as ASTLiteral;
1343
+ if (cronArg.type === 'Literal') {
1344
+ const expression = String(cronArg.value);
1345
+ try {
1346
+ parseCronExpression(expression, {
1347
+ hasSeconds: false,
1348
+ });
1349
+ } catch (ex) {
1350
+ throw new InvalidRouterConfigError({
1351
+ filename,
1352
+ cause: ex,
1353
+ line: body.loc?.start?.line,
1354
+ message: `invalid cron expression "${expression}" in ${filename} at line ${body.loc?.start?.line}`,
1355
+ });
1356
+ }
1357
+ config = { expression };
1358
+ }
1359
+ }
1360
+ break;
1361
+ }
1362
+ }
1363
+ }
1318
1364
  } else {
1319
1365
  throw new InvalidRouterConfigError({
1320
1366
  filename,
@@ -1327,6 +1373,8 @@ export async function parseRoute(
1327
1373
  case 'stream':
1328
1374
  case 'sse':
1329
1375
  case 'websocket': {
1376
+ // DEPRECATED: router.stream(), router.sse(), router.websocket()
1377
+ // These methods now throw errors at runtime
1330
1378
  type = method;
1331
1379
  method = 'post';
1332
1380
  const theaction = action as ASTLiteral;
@@ -1336,39 +1384,9 @@ export async function parseRoute(
1336
1384
  }
1337
1385
  break;
1338
1386
  }
1339
- case 'sms': {
1340
- type = method;
1341
- method = 'post';
1342
- const theaction = action as ASTObjectExpression;
1343
- if (theaction.type === 'ObjectExpression') {
1344
- config = {};
1345
- theaction.properties.forEach((p) => {
1346
- if (p.value.type === 'Literal') {
1347
- const literal = p.value as ASTLiteral;
1348
- config![p.key.name] = literal.value;
1349
- }
1350
- });
1351
- const number = theaction.properties.find((p) => p.key.name === 'number');
1352
- if (number && number.value.type === 'Literal') {
1353
- const phoneNumber = number.value as ASTLiteral;
1354
- suffix = hash(String(phoneNumber.value));
1355
- break;
1356
- }
1357
- }
1358
- break;
1359
- }
1360
- case 'email': {
1361
- type = method;
1362
- method = 'post';
1363
- const theaction = action as ASTLiteral;
1364
- if (theaction.type === 'Literal') {
1365
- const email = String(theaction.value);
1366
- suffix = hash(email);
1367
- break;
1368
- }
1369
- break;
1370
- }
1371
1387
  case 'cron': {
1388
+ // DEPRECATED: router.cron()
1389
+ // This method now throws errors at runtime
1372
1390
  type = method;
1373
1391
  method = 'post';
1374
1392
  const theaction = action as ASTLiteral;
@@ -1437,8 +1455,8 @@ export async function parseRoute(
1437
1455
  }
1438
1456
  }
1439
1457
 
1440
- // For WebSocket/SSE routes that don't use validator(), fall back to exported schemas
1441
- if (!routeConfig.hasValidator && (type === 'websocket' || type === 'sse')) {
1458
+ // For WebSocket/SSE/stream routes that don't use validator(), fall back to exported schemas
1459
+ if (!routeConfig.hasValidator && (type === 'websocket' || type === 'sse' || type === 'stream')) {
1442
1460
  if (!routeConfig.inputSchemaVariable && exportedInputSchemaName) {
1443
1461
  routeConfig.inputSchemaVariable = exportedInputSchemaName;
1444
1462
  }
@@ -131,10 +131,10 @@ app.route('/', workbenchRouter);
131
131
  `
132
132
  : '';
133
133
 
134
- // Asset proxy routes - generated for dev mode, reads VITE_PORT from env at runtime
135
- const assetProxyRoutes =
136
- mode === 'dev'
137
- ? `
134
+ // Asset proxy routes - Always generated, but only active at runtime when:
135
+ // - NODE_ENV !== 'production' (isDevelopment())
136
+ // - and process.env.VITE_PORT is set
137
+ const assetProxyRoutes = `
138
138
  // Asset proxy routes - Development mode only (proxies to Vite asset server)
139
139
  if (isDevelopment() && process.env.VITE_PORT) {
140
140
  const VITE_ASSET_PORT = parseInt(process.env.VITE_PORT, 10);
@@ -193,8 +193,7 @@ if (isDevelopment() && process.env.VITE_PORT) {
193
193
  app.get('/*.tsx', proxyToVite);
194
194
  app.get('/*.css', proxyToVite);
195
195
  }
196
- `
197
- : '';
196
+ `;
198
197
 
199
198
  // Runtime mode detection helper (defined at top level for reuse)
200
199
  // Dynamic property access prevents Bun.build from inlining NODE_ENV at build time
@@ -330,13 +329,18 @@ if (isDevelopment()) {
330
329
  if (typeof Bun !== 'undefined') {
331
330
  // Enable process exit protection now that we're starting the server
332
331
  enableProcessExitProtection();
333
-
332
+
334
333
  const port = parseInt(process.env.PORT || '3500', 10);
335
334
  const server = Bun.serve({
336
- fetch: app.fetch,
335
+ fetch: (req, server) => {
336
+ // Get timeout from config on each request (0 = no timeout)
337
+ server.timeout(req, getAppConfig()?.requestTimeout ?? 0);
338
+ return app.fetch(req, server);
339
+ },
337
340
  websocket,
338
341
  port,
339
342
  hostname: '127.0.0.1',
343
+ development: isDevelopment(),
340
344
  });
341
345
 
342
346
  // Make server available globally for health checks
@@ -428,16 +432,6 @@ if (isDevelopment()) {
428
432
  const serverUrl = \`http://127.0.0.1:\${process.env.PORT || '3500'}\`;
429
433
  const otel = register({ processors: [], logLevel: (process.env.AGENTUITY_LOG_LEVEL || 'info') as LogLevel });
430
434
 
431
- // Get app state and config for use below
432
- const appState = getAppState();
433
- const appConfig = getAppConfig();
434
-
435
- createServices(otel.logger, appConfig, serverUrl);
436
-
437
- // Make logger and tracer globally available for user's app.ts
438
- setGlobalLogger(otel.logger);
439
- setGlobalTracer(otel.tracer);
440
-
441
435
  // Step 2: Create router and set as global
442
436
  const app = createRouter();
443
437
  setGlobalRouter(app);
@@ -466,6 +460,15 @@ app.use('/api/*', createAgentMiddleware(''));
466
460
  await import('../../app.js');
467
461
 
468
462
  // Step 5: Initialize providers
463
+ const appState = getAppState();
464
+ const appConfig = getAppConfig();
465
+
466
+ createServices(otel.logger, appConfig, serverUrl);
467
+
468
+ // Make logger and tracer globally available for user's app.ts
469
+ setGlobalLogger(otel.logger);
470
+ setGlobalTracer(otel.tracer);
471
+
469
472
  const threadProvider = getThreadProvider();
470
473
  const sessionProvider = getSessionProvider();
471
474
 
@@ -397,13 +397,15 @@ function extractEvalsFromSource(
397
397
 
398
398
  if (isCreateEvalCall) {
399
399
  const callExpr = n as unknown as ASTCallExpression;
400
+ let evalName: string | undefined;
401
+ let description: string | undefined;
402
+
400
403
  if (callExpr.arguments.length >= 2) {
404
+ // Format: agent.createEval('name', { config })
401
405
  const nameArg = callExpr.arguments[0] as ASTLiteral;
402
- const evalName = String(nameArg.value);
406
+ evalName = String(nameArg.value);
403
407
 
404
408
  const callargexp = callExpr.arguments[1] as ASTObjectExpression;
405
- let description: string | undefined;
406
-
407
409
  if (callargexp.properties) {
408
410
  for (const prop of callargexp.properties) {
409
411
  if (prop.key.name === 'metadata' && prop.value.type === 'ObjectExpression') {
@@ -415,7 +417,47 @@ function extractEvalsFromSource(
415
417
  }
416
418
  }
417
419
  }
420
+ } else if (callExpr.arguments.length === 1) {
421
+ // Format: agent.createEval(presetEval({ name: '...', ... }))
422
+ // or: agent.createEval(presetEval()) - uses preset's default name
423
+ // or: agent.createEval({ name: '...', ... })
424
+ const arg = callExpr.arguments[0] as ASTNode;
425
+
426
+ // Handle CallExpression: presetEval({ name: '...' }) or presetEval()
427
+ if (arg.type === 'CallExpression') {
428
+ const innerCall = arg as unknown as ASTCallExpression;
429
+
430
+ // Try to get name from the call arguments first
431
+ if (innerCall.arguments.length >= 1) {
432
+ const configArg = innerCall.arguments[0] as ASTObjectExpression;
433
+ if (configArg.type === 'ObjectExpression' && configArg.properties) {
434
+ const configMap = parseObjectExpressionToMap(configArg);
435
+ evalName = configMap.get('name');
436
+ description = configMap.get('description');
437
+ }
438
+ }
439
+
440
+ // Fallback: use the callee name as the eval name (e.g., politeness())
441
+ if (!evalName && innerCall.callee) {
442
+ const callee = innerCall.callee as ASTNode;
443
+ if (callee.type === 'Identifier') {
444
+ evalName = (callee as ASTNodeIdentifier).name;
445
+ }
446
+ }
447
+ }
448
+
449
+ // Handle ObjectExpression: { name: '...', handler: ... }
450
+ if (arg.type === 'ObjectExpression') {
451
+ const configArg = arg as ASTObjectExpression;
452
+ if (configArg.properties) {
453
+ const configMap = parseObjectExpressionToMap(configArg);
454
+ evalName = configMap.get('name');
455
+ description = configMap.get('description');
456
+ }
457
+ }
458
+ }
418
459
 
460
+ if (evalName) {
419
461
  const id = getEvalId(projectId, deploymentId, filename, evalName, version);
420
462
  const evalId = generateStableEvalId(projectId, agentId, evalName);
421
463
 
@@ -95,7 +95,7 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
95
95
  }
96
96
  }
97
97
  } catch (error) {
98
- logger.info('Failed to load agentuity.config.ts for externals:', error);
98
+ logger.debug('Failed to load agentuity.config.ts for externals:', error);
99
99
  }
100
100
  }
101
101
 
@@ -49,11 +49,63 @@ export async function startViteAssetServer(
49
49
  // Create Vite server with config
50
50
  const server = await createServer(config);
51
51
 
52
- // Start listening - Vite will choose alternate port if preferred is taken
53
- await server.listen();
52
+ // Start listening with timeout to prevent hangs
53
+ // Vite will choose alternate port if preferred is taken
54
+ const STARTUP_TIMEOUT_MS = 30000; // 30 seconds
54
55
 
55
- // Get the actual port Vite is using (may differ from preferred if port was taken)
56
- const actualPort = server.config.server.port || preferredPort;
56
+ try {
57
+ await Promise.race([
58
+ server.listen(),
59
+ new Promise<never>((_, reject) => {
60
+ const timeoutId = setTimeout(() => {
61
+ reject(
62
+ new Error(
63
+ `Vite asset server failed to start within ${STARTUP_TIMEOUT_MS / 1000}s`
64
+ )
65
+ );
66
+ }, STARTUP_TIMEOUT_MS);
67
+ // Clean up timeout when listen succeeds (via finally in the outer try)
68
+ server.httpServer?.once('listening', () => clearTimeout(timeoutId));
69
+ }),
70
+ ]);
71
+ } catch (error) {
72
+ // Attempt to close the server on failure
73
+ try {
74
+ await server.close();
75
+ } catch {
76
+ // Ignore close errors
77
+ }
78
+ throw error;
79
+ }
80
+
81
+ // Helper to get port from httpServer
82
+ const getPortFromHttpServer = (): number | null => {
83
+ const httpServer = server.httpServer;
84
+ if (httpServer) {
85
+ const address = httpServer.address();
86
+ if (address && typeof address === 'object' && 'port' in address) {
87
+ return address.port;
88
+ }
89
+ }
90
+ return null;
91
+ };
92
+
93
+ // Get the actual port Vite is using from the httpServer
94
+ // server.config.server.port is the requested port, not the actual one
95
+ let actualPort = preferredPort;
96
+
97
+ // The resolved URL contains the actual port being used
98
+ const resolvedUrls = server.resolvedUrls;
99
+ if (resolvedUrls?.local && resolvedUrls.local.length > 0) {
100
+ try {
101
+ const url = new URL(resolvedUrls.local[0]);
102
+ actualPort = parseInt(url.port, 10) || preferredPort;
103
+ } catch {
104
+ actualPort = getPortFromHttpServer() ?? preferredPort;
105
+ }
106
+ } else {
107
+ actualPort = getPortFromHttpServer() ?? preferredPort;
108
+ }
57
109
 
58
110
  logger.debug(`✅ Vite asset server started on port ${actualPort}`);
59
111
  if (actualPort !== preferredPort) {
@@ -240,6 +240,10 @@ interface BuildResult {
240
240
  export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Promise<BuildResult> {
241
241
  const { rootDir, projectId = '', dev = false, logger } = options;
242
242
 
243
+ if (!dev) {
244
+ rmSync(join(rootDir, '.agentuity'), { force: true, recursive: true });
245
+ }
246
+
243
247
  const result: BuildResult = {
244
248
  workbench: { included: false, duration: 0 },
245
249
  client: { included: false, duration: 0 },
@@ -250,7 +254,6 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
250
254
  const { loadAgentuityConfig, getWorkbenchConfig } = await import('./config-loader');
251
255
  const config = await loadAgentuityConfig(rootDir, logger);
252
256
  const workbenchConfig = getWorkbenchConfig(config, dev);
253
-
254
257
  // Generate workbench files BEFORE any builds if enabled (dev mode only)
255
258
  if (workbenchConfig.enabled) {
256
259
  logger.debug('Workbench enabled (dev mode), generating files before build...');