@agentuity/cli 0.0.101 → 0.0.102

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 (210) hide show
  1. package/AGENTS.md +19 -188
  2. package/bin/cli.ts +13 -6
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +41 -12
  5. package/dist/cli.js.map +1 -1
  6. package/dist/cmd/ai/index.d.ts.map +1 -1
  7. package/dist/cmd/ai/index.js +6 -1
  8. package/dist/cmd/ai/index.js.map +1 -1
  9. package/dist/cmd/ai/prompt/agent.d.ts +7 -0
  10. package/dist/cmd/ai/prompt/agent.d.ts.map +1 -1
  11. package/dist/cmd/ai/prompt/agent.js +12 -323
  12. package/dist/cmd/ai/prompt/agent.js.map +1 -1
  13. package/dist/cmd/ai/prompt/api.d.ts +7 -0
  14. package/dist/cmd/ai/prompt/api.d.ts.map +1 -1
  15. package/dist/cmd/ai/prompt/api.js +12 -260
  16. package/dist/cmd/ai/prompt/api.js.map +1 -1
  17. package/dist/cmd/ai/prompt/version.d.ts +35 -0
  18. package/dist/cmd/ai/prompt/version.d.ts.map +1 -0
  19. package/dist/cmd/ai/prompt/version.js +55 -0
  20. package/dist/cmd/ai/prompt/version.js.map +1 -0
  21. package/dist/cmd/ai/prompt/web.d.ts +7 -0
  22. package/dist/cmd/ai/prompt/web.d.ts.map +1 -1
  23. package/dist/cmd/ai/prompt/web.js +12 -283
  24. package/dist/cmd/ai/prompt/web.js.map +1 -1
  25. package/dist/cmd/ai/skills/generate.d.ts +3 -0
  26. package/dist/cmd/ai/skills/generate.d.ts.map +1 -0
  27. package/dist/cmd/ai/skills/generate.js +65 -0
  28. package/dist/cmd/ai/skills/generate.js.map +1 -0
  29. package/dist/cmd/ai/skills/generator.d.ts +4 -0
  30. package/dist/cmd/ai/skills/generator.d.ts.map +1 -0
  31. package/dist/cmd/ai/skills/generator.js +402 -0
  32. package/dist/cmd/ai/skills/generator.js.map +1 -0
  33. package/dist/cmd/ai/skills/index.d.ts +4 -0
  34. package/dist/cmd/ai/skills/index.d.ts.map +1 -0
  35. package/dist/cmd/ai/skills/index.js +21 -0
  36. package/dist/cmd/ai/skills/index.js.map +1 -0
  37. package/dist/cmd/auth/signup.d.ts.map +1 -1
  38. package/dist/cmd/auth/signup.js +1 -0
  39. package/dist/cmd/auth/signup.js.map +1 -1
  40. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  41. package/dist/cmd/build/entry-generator.js +40 -5
  42. package/dist/cmd/build/entry-generator.js.map +1 -1
  43. package/dist/cmd/build/vite/bun-dev-server.d.ts +7 -1
  44. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/bun-dev-server.js +52 -26
  46. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  47. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  48. package/dist/cmd/build/vite/metadata-generator.js +58 -7
  49. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  50. package/dist/cmd/build/vite/prompt-generator.d.ts +23 -0
  51. package/dist/cmd/build/vite/prompt-generator.d.ts.map +1 -0
  52. package/dist/cmd/build/vite/prompt-generator.js +123 -0
  53. package/dist/cmd/build/vite/prompt-generator.js.map +1 -0
  54. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  55. package/dist/cmd/build/vite/registry-generator.js +28 -11
  56. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  57. package/dist/cmd/build/vite/server-bundler.d.ts +4 -0
  58. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  59. package/dist/cmd/build/vite/server-bundler.js +45 -16
  60. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  61. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  62. package/dist/cmd/build/vite/vite-asset-server-config.js +4 -0
  63. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  64. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  65. package/dist/cmd/build/vite/vite-builder.js +99 -87
  66. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  67. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  68. package/dist/cmd/cloud/deploy.js +78 -27
  69. package/dist/cmd/cloud/deploy.js.map +1 -1
  70. package/dist/cmd/cloud/keyvalue/create-namespace.d.ts.map +1 -1
  71. package/dist/cmd/cloud/keyvalue/create-namespace.js +3 -1
  72. package/dist/cmd/cloud/keyvalue/create-namespace.js.map +1 -1
  73. package/dist/cmd/cloud/keyvalue/delete-namespace.d.ts.map +1 -1
  74. package/dist/cmd/cloud/keyvalue/delete-namespace.js +3 -1
  75. package/dist/cmd/cloud/keyvalue/delete-namespace.js.map +1 -1
  76. package/dist/cmd/cloud/keyvalue/delete.d.ts.map +1 -1
  77. package/dist/cmd/cloud/keyvalue/delete.js +3 -1
  78. package/dist/cmd/cloud/keyvalue/delete.js.map +1 -1
  79. package/dist/cmd/cloud/keyvalue/set.d.ts.map +1 -1
  80. package/dist/cmd/cloud/keyvalue/set.js +4 -2
  81. package/dist/cmd/cloud/keyvalue/set.js.map +1 -1
  82. package/dist/cmd/cloud/stream/get.d.ts.map +1 -1
  83. package/dist/cmd/cloud/stream/get.js +2 -13
  84. package/dist/cmd/cloud/stream/get.js.map +1 -1
  85. package/dist/cmd/cloud/vector/delete-namespace.d.ts +3 -0
  86. package/dist/cmd/cloud/vector/delete-namespace.d.ts.map +1 -0
  87. package/dist/cmd/cloud/vector/delete-namespace.js +77 -0
  88. package/dist/cmd/cloud/vector/delete-namespace.js.map +1 -0
  89. package/dist/cmd/cloud/vector/index.d.ts.map +1 -1
  90. package/dist/cmd/cloud/vector/index.js +21 -4
  91. package/dist/cmd/cloud/vector/index.js.map +1 -1
  92. package/dist/cmd/cloud/vector/list-namespaces.d.ts +3 -0
  93. package/dist/cmd/cloud/vector/list-namespaces.d.ts.map +1 -0
  94. package/dist/cmd/cloud/vector/list-namespaces.js +42 -0
  95. package/dist/cmd/cloud/vector/list-namespaces.js.map +1 -0
  96. package/dist/cmd/cloud/vector/stats.d.ts +3 -0
  97. package/dist/cmd/cloud/vector/stats.d.ts.map +1 -0
  98. package/dist/cmd/cloud/vector/stats.js +142 -0
  99. package/dist/cmd/cloud/vector/stats.js.map +1 -0
  100. package/dist/cmd/cloud/vector/upsert.d.ts +3 -0
  101. package/dist/cmd/cloud/vector/upsert.d.ts.map +1 -0
  102. package/dist/cmd/cloud/vector/upsert.js +192 -0
  103. package/dist/cmd/cloud/vector/upsert.js.map +1 -0
  104. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  105. package/dist/cmd/dev/file-watcher.js +90 -31
  106. package/dist/cmd/dev/file-watcher.js.map +1 -1
  107. package/dist/cmd/dev/index.d.ts.map +1 -1
  108. package/dist/cmd/dev/index.js +213 -57
  109. package/dist/cmd/dev/index.js.map +1 -1
  110. package/dist/cmd/dev/skills.d.ts +10 -0
  111. package/dist/cmd/dev/skills.d.ts.map +1 -0
  112. package/dist/cmd/dev/skills.js +57 -0
  113. package/dist/cmd/dev/skills.js.map +1 -0
  114. package/dist/cmd/dev/sync.js +7 -7
  115. package/dist/cmd/dev/sync.js.map +1 -1
  116. package/dist/cmd/index.d.ts.map +1 -1
  117. package/dist/cmd/index.js +1 -0
  118. package/dist/cmd/index.js.map +1 -1
  119. package/dist/cmd/project/create.d.ts.map +1 -1
  120. package/dist/cmd/project/create.js +3 -0
  121. package/dist/cmd/project/create.js.map +1 -1
  122. package/dist/cmd/project/template-flow.d.ts +1 -0
  123. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  124. package/dist/cmd/project/template-flow.js +30 -5
  125. package/dist/cmd/project/template-flow.js.map +1 -1
  126. package/dist/cmd/setup/index.d.ts.map +1 -1
  127. package/dist/cmd/setup/index.js +1 -0
  128. package/dist/cmd/setup/index.js.map +1 -1
  129. package/dist/cmd/upgrade/index.d.ts +15 -0
  130. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  131. package/dist/cmd/upgrade/index.js +59 -4
  132. package/dist/cmd/upgrade/index.js.map +1 -1
  133. package/dist/domain.d.ts +45 -0
  134. package/dist/domain.d.ts.map +1 -0
  135. package/dist/domain.js +200 -0
  136. package/dist/domain.js.map +1 -0
  137. package/dist/schema-generator.d.ts +2 -0
  138. package/dist/schema-generator.d.ts.map +1 -1
  139. package/dist/schema-generator.js +18 -0
  140. package/dist/schema-generator.js.map +1 -1
  141. package/dist/steps.d.ts +1 -1
  142. package/dist/steps.d.ts.map +1 -1
  143. package/dist/steps.js +16 -5
  144. package/dist/steps.js.map +1 -1
  145. package/dist/tui/prompt.d.ts +1 -2
  146. package/dist/tui/prompt.d.ts.map +1 -1
  147. package/dist/tui/prompt.js +8 -4
  148. package/dist/tui/prompt.js.map +1 -1
  149. package/dist/tui.d.ts +16 -0
  150. package/dist/tui.d.ts.map +1 -1
  151. package/dist/tui.js +23 -2
  152. package/dist/tui.js.map +1 -1
  153. package/dist/types.d.ts +9 -2
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +3 -3
  156. package/dist/types.js.map +1 -1
  157. package/package.json +4 -4
  158. package/src/cli.ts +47 -12
  159. package/src/cmd/ai/index.ts +6 -1
  160. package/src/cmd/ai/prompt/agent.md +306 -0
  161. package/src/cmd/ai/prompt/agent.ts +12 -322
  162. package/src/cmd/ai/prompt/api.md +360 -0
  163. package/src/cmd/ai/prompt/api.ts +13 -260
  164. package/src/cmd/ai/prompt/version.ts +61 -0
  165. package/src/cmd/ai/prompt/web.md +509 -0
  166. package/src/cmd/ai/prompt/web.ts +12 -282
  167. package/src/cmd/ai/skills/generate.ts +75 -0
  168. package/src/cmd/ai/skills/generator.ts +519 -0
  169. package/src/cmd/ai/skills/index.ts +23 -0
  170. package/src/cmd/auth/signup.ts +1 -0
  171. package/src/cmd/build/entry-generator.ts +43 -7
  172. package/src/cmd/build/vite/bun-dev-server.ts +57 -27
  173. package/src/cmd/build/vite/metadata-generator.ts +73 -7
  174. package/src/cmd/build/vite/prompt-generator.ts +169 -0
  175. package/src/cmd/build/vite/registry-generator.ts +33 -10
  176. package/src/cmd/build/vite/server-bundler.ts +53 -22
  177. package/src/cmd/build/vite/vite-asset-server-config.ts +5 -0
  178. package/src/cmd/build/vite/vite-builder.ts +107 -87
  179. package/src/cmd/cloud/deploy.ts +99 -31
  180. package/src/cmd/cloud/keyvalue/create-namespace.ts +3 -1
  181. package/src/cmd/cloud/keyvalue/delete-namespace.ts +3 -1
  182. package/src/cmd/cloud/keyvalue/delete.ts +3 -1
  183. package/src/cmd/cloud/keyvalue/set.ts +4 -2
  184. package/src/cmd/cloud/stream/get.ts +2 -9
  185. package/src/cmd/cloud/vector/delete-namespace.ts +89 -0
  186. package/src/cmd/cloud/vector/index.ts +21 -4
  187. package/src/cmd/cloud/vector/list-namespaces.ts +46 -0
  188. package/src/cmd/cloud/vector/stats.ts +160 -0
  189. package/src/cmd/cloud/vector/upsert.ts +216 -0
  190. package/src/cmd/dev/file-watcher.ts +101 -32
  191. package/src/cmd/dev/index.ts +304 -111
  192. package/src/cmd/dev/skills.ts +82 -0
  193. package/src/cmd/dev/sync.ts +7 -7
  194. package/src/cmd/index.ts +1 -0
  195. package/src/cmd/project/create.ts +3 -0
  196. package/src/cmd/project/template-flow.ts +37 -5
  197. package/src/cmd/setup/index.ts +1 -0
  198. package/src/cmd/upgrade/index.ts +68 -4
  199. package/src/domain.ts +273 -0
  200. package/src/schema-generator.ts +23 -0
  201. package/src/steps.ts +16 -5
  202. package/src/tui/prompt.ts +11 -5
  203. package/src/tui.ts +21 -2
  204. package/src/types/md.d.ts +8 -0
  205. package/src/types.ts +12 -3
  206. package/dist/cmd/cloud/domain.d.ts +0 -17
  207. package/dist/cmd/cloud/domain.d.ts.map +0 -1
  208. package/dist/cmd/cloud/domain.js +0 -79
  209. package/dist/cmd/cloud/domain.js.map +0 -1
  210. package/src/cmd/cloud/domain.ts +0 -100
@@ -15,6 +15,7 @@ import { getDevmodeDeploymentId } from '../build/ast';
15
15
  import { getDefaultConfigDir, saveConfig } from '../../config';
16
16
  import type { Config } from '../../types';
17
17
  import { createFileWatcher } from './file-watcher';
18
+ import { regenerateSkillsAsync } from './skills';
18
19
 
19
20
  const DEFAULT_PORT = 3500;
20
21
  const MIN_PORT = 1024;
@@ -32,6 +33,81 @@ interface ServerLike {
32
33
  close: () => void;
33
34
  }
34
35
 
36
+ interface BunServer {
37
+ stop: (closeActiveConnections?: boolean) => void;
38
+ port: number;
39
+ }
40
+
41
+ /**
42
+ * Kill any lingering gravity processes from previous dev sessions.
43
+ * This is a defensive measure to clean up orphaned processes.
44
+ */
45
+ async function killLingeringGravityProcesses(
46
+ logger: { debug: (msg: string, ...args: unknown[]) => void }
47
+ ): Promise<void> {
48
+ // Only attempt on Unix-like systems (macOS, Linux)
49
+ if (process.platform === 'win32') {
50
+ return;
51
+ }
52
+
53
+ try {
54
+ // Use pkill to kill gravity processes owned by current user
55
+ // The -f flag matches against full command line
56
+ // We specifically match the gravity binary name to avoid killing unrelated processes
57
+ const result = Bun.spawnSync(['pkill', '-f', 'gravity.*--endpoint-id'], {
58
+ stdout: 'ignore',
59
+ stderr: 'ignore',
60
+ });
61
+
62
+ // Exit code 0 = processes killed, 1 = no matching processes, other = error
63
+ if (result.exitCode === 0) {
64
+ logger.debug('Killed lingering gravity processes from previous session');
65
+ // Brief pause to let processes fully terminate
66
+ await new Promise((resolve) => setTimeout(resolve, 100));
67
+ }
68
+ } catch {
69
+ // pkill not available or failed - not critical, continue
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Stop the existing Bun server if one is running.
75
+ * Waits for the port to become available before returning.
76
+ */
77
+ async function stopBunServer(
78
+ port: number,
79
+ logger: { debug: (msg: string, ...args: unknown[]) => void }
80
+ ): Promise<void> {
81
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
+ const globalAny = globalThis as any;
83
+ const server = globalAny.__AGENTUITY_SERVER__ as BunServer | undefined;
84
+ if (!server) return;
85
+
86
+ try {
87
+ logger.debug('Stopping previous Bun server...');
88
+ server.stop(true); // Close active connections immediately
89
+ } catch (err) {
90
+ logger.debug('Error stopping previous Bun server: %s', err);
91
+ }
92
+
93
+ // Wait for socket to close to avoid EADDRINUSE races
94
+ for (let i = 0; i < 20; i++) {
95
+ try {
96
+ await fetch(`http://127.0.0.1:${port}/`, {
97
+ method: 'HEAD',
98
+ signal: AbortSignal.timeout(200),
99
+ });
100
+ // Still responding, wait a bit more
101
+ await new Promise((r) => setTimeout(r, 50));
102
+ } catch {
103
+ // Connection refused or timeout => server is down
104
+ break;
105
+ }
106
+ }
107
+
108
+ globalAny.__AGENTUITY_SERVER__ = undefined;
109
+ }
110
+
35
111
  const getDefaultPort = (): number => {
36
112
  const envPort = process.env.PORT;
37
113
  if (!envPort) {
@@ -120,6 +196,10 @@ export const command = createCommand({
120
196
  originalExit(1);
121
197
  }
122
198
 
199
+ // Kill any lingering gravity processes from previous dev sessions
200
+ // This prevents "zombie" gravity clients from blocking the public URL
201
+ await killLingeringGravityProcesses(logger);
202
+
123
203
  // Setup devmode and gravity (if using public URL)
124
204
  const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
125
205
  const apiClient = auth ? new APIClient(getAPIBaseURL(config), logger, config) : null;
@@ -132,7 +212,9 @@ export const command = createCommand({
132
212
  : null;
133
213
 
134
214
  // Track previous metadata for sync diffing
135
- let previousMetadata: Awaited<ReturnType<typeof import('../build/vite/metadata-generator').generateMetadata>> | undefined;
215
+ let previousMetadata:
216
+ | Awaited<ReturnType<typeof import('../build/vite/metadata-generator').generateMetadata>>
217
+ | undefined;
136
218
 
137
219
  let devmode: DevmodeResponse | undefined;
138
220
  let gravityBin: string | undefined;
@@ -234,6 +316,12 @@ export const command = createCommand({
234
316
  centerTitle: false,
235
317
  });
236
318
 
319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
+ const cliVersion = ((global as any).__CLI_SCHEMA__?.version as string) ?? '';
321
+ if (cliVersion) {
322
+ regenerateSkillsAsync(rootDir, cliVersion, logger).catch(() => {});
323
+ }
324
+
237
325
  // Start Vite asset server ONCE before restart loop
238
326
  // Vite handles frontend HMR independently and stays running across backend restarts
239
327
  let viteServer: ServerLike | null = null;
@@ -281,16 +369,36 @@ export const command = createCommand({
281
369
  // Start file watcher (will be paused during builds)
282
370
  fileWatcher.start();
283
371
 
284
- // Setup signal handlers once before the loop
285
- const cleanup = async () => {
286
- tui.info('Shutting down...');
372
+ // Track if cleanup is in progress to avoid duplicate cleanup
373
+ let cleaningUp = false;
374
+
375
+ /**
376
+ * Centralized cleanup function for all resources.
377
+ * Called on restart, shutdown, and fatal errors.
378
+ * @param exitAfter - If true, exit the process after cleanup
379
+ * @param exitCode - Exit code to use if exitAfter is true
380
+ * @param silent - If true, don't show "Shutting down" message
381
+ */
382
+ const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
383
+ if (cleaningUp) return;
384
+ cleaningUp = true;
385
+
386
+ if (!silent) {
387
+ tui.info('Shutting down...');
388
+ }
287
389
 
288
- // Stop file watcher
289
- fileWatcher.stop();
390
+ // Stop file watcher first to prevent restart triggers during cleanup
391
+ try {
392
+ fileWatcher.stop();
393
+ } catch (err) {
394
+ logger.debug('Error stopping file watcher: %s', err);
395
+ }
290
396
 
291
- // Close Vite asset server first
292
- if (viteServer) {
293
- await viteServer.close();
397
+ // Stop Bun server
398
+ try {
399
+ await stopBunServer(opts.port, logger);
400
+ } catch (err) {
401
+ logger.debug('Error stopping Bun server during cleanup: %s', err);
294
402
  }
295
403
 
296
404
  // Kill gravity client with SIGTERM first, then SIGKILL as fallback
@@ -298,37 +406,114 @@ export const command = createCommand({
298
406
  try {
299
407
  gravityProcess.kill('SIGTERM');
300
408
  // Give it a moment to gracefully shutdown
301
- await new Promise((resolve) => setTimeout(resolve, 100));
409
+ await new Promise((resolve) => setTimeout(resolve, 150));
302
410
  if (gravityProcess.exitCode === null) {
303
411
  gravityProcess.kill('SIGKILL');
304
412
  }
305
413
  } catch (err) {
306
414
  logger.debug('Error killing gravity process: %s', err);
415
+ } finally {
416
+ gravityProcess = null;
307
417
  }
308
418
  }
309
419
 
310
- originalExit(0);
420
+ // Close Vite asset server last (it handles frontend, should stay up longest)
421
+ if (viteServer) {
422
+ try {
423
+ await viteServer.close();
424
+ } catch (err) {
425
+ logger.debug('Error closing Vite server: %s', err);
426
+ } finally {
427
+ viteServer = null;
428
+ }
429
+ }
430
+
431
+ // Reset cleanup flag if not exiting (allows restart)
432
+ if (!exitAfter) {
433
+ cleaningUp = false;
434
+ } else {
435
+ originalExit(exitCode);
436
+ }
437
+ };
438
+
439
+ /**
440
+ * Cleanup for restart: stops Bun server and Gravity, keeps Vite running
441
+ */
442
+ const cleanupForRestart = async () => {
443
+ logger.debug('Cleaning up for restart...');
444
+
445
+ // Stop Bun server
446
+ try {
447
+ await stopBunServer(opts.port, logger);
448
+ } catch (err) {
449
+ logger.debug('Error stopping Bun server for restart: %s', err);
450
+ }
451
+
452
+ // Kill gravity client
453
+ if (gravityProcess) {
454
+ try {
455
+ gravityProcess.kill('SIGTERM');
456
+ await new Promise((resolve) => setTimeout(resolve, 150));
457
+ if (gravityProcess.exitCode === null) {
458
+ gravityProcess.kill('SIGKILL');
459
+ }
460
+ } catch (err) {
461
+ logger.debug('Error killing gravity process for restart: %s', err);
462
+ } finally {
463
+ gravityProcess = null;
464
+ }
465
+ }
311
466
  };
312
467
 
313
468
  // 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
- });
469
+ let signalHandlersRegistered = false;
470
+ if (!signalHandlersRegistered) {
471
+ signalHandlersRegistered = true;
472
+
473
+ const safeExit = async (code: number, reason?: string) => {
474
+ if (reason) {
475
+ logger.debug('DevMode terminating (%d) due to: %s', code, reason);
476
+ }
477
+ await cleanup(true, code);
478
+ };
479
+
480
+ process.on('SIGINT', () => {
481
+ void safeExit(0, 'SIGINT');
482
+ });
326
483
 
327
- // Ensure Vite and gravity are always killed on exit (even if cleanup is bypassed)
484
+ process.on('SIGTERM', () => {
485
+ void safeExit(0, 'SIGTERM');
486
+ });
487
+
488
+ // Handle uncaught exceptions - clean up and exit rather than limping on
489
+ process.on('uncaughtException', (err) => {
490
+ tui.error(
491
+ `Uncaught exception: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
492
+ );
493
+ void safeExit(1, 'uncaughtException');
494
+ });
495
+
496
+ // Handle unhandled rejections - log but don't exit (usually recoverable)
497
+ process.on('unhandledRejection', (reason) => {
498
+ logger.warn(
499
+ 'Unhandled promise rejection: %s',
500
+ reason instanceof Error ? reason.stack ?? reason.message : String(reason)
501
+ );
502
+ });
503
+ }
504
+
505
+ // Ensure resources are always cleaned up on exit (synchronous fallback)
328
506
  process.on('exit', () => {
507
+ // Kill gravity client with SIGKILL for immediate termination
508
+ if (gravityProcess && gravityProcess.exitCode === null) {
509
+ try {
510
+ gravityProcess.kill('SIGKILL');
511
+ } catch {
512
+ // Ignore errors during exit cleanup
513
+ }
514
+ }
515
+
329
516
  // Close Vite server synchronously if possible
330
- // Note: Vite's close() is async, but we can't await in 'exit' handler
331
- // Most Vite implementations handle sync close gracefully
332
517
  if (viteServer) {
333
518
  try {
334
519
  viteServer.close();
@@ -337,10 +522,12 @@ export const command = createCommand({
337
522
  }
338
523
  }
339
524
 
340
- // Kill gravity client with SIGKILL for immediate termination
341
- if (gravityProcess && gravityProcess.exitCode === null) {
525
+ // Stop Bun server synchronously (best effort)
526
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
527
+ const server = (globalThis as any).__AGENTUITY_SERVER__;
528
+ if (server?.stop) {
342
529
  try {
343
- gravityProcess.kill('SIGKILL');
530
+ server.stop(true);
344
531
  } catch {
345
532
  // Ignore errors during exit cleanup
346
533
  }
@@ -358,67 +545,90 @@ export const command = createCommand({
358
545
  await tui.spinner({
359
546
  message: 'Building dev bundle',
360
547
  callback: async () => {
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
- });
548
+ // Step 1: Generate workbench files if enabled (must be done before entry generation)
549
+ if (workbenchConfigData.enabled) {
550
+ logger.debug('Workbench enabled, generating source files before bundle...');
551
+ const { generateWorkbenchFiles } = await import(
552
+ '../build/vite/workbench-generator'
553
+ );
554
+ await generateWorkbenchFiles(
555
+ rootDir,
556
+ project?.projectId ?? '',
557
+ workbenchConfigData,
558
+ logger
559
+ );
560
+ }
409
561
 
410
- writeMetadataFile(rootDir, metadata, true, logger);
562
+ // Step 2: Generate entry file with workbench config
563
+ // Note: vitePort is NOT passed here - the app reads process.env.VITE_PORT at runtime
564
+ const { generateEntryFile } = await import('../build/entry-generator');
565
+ await generateEntryFile({
566
+ rootDir,
567
+ projectId: project?.projectId ?? '',
568
+ deploymentId,
569
+ logger,
570
+ mode: 'dev',
571
+ workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
572
+ });
573
+
574
+ // Step 3: Bundle the app with LLM patches (dev mode = no minification)
575
+ // This produces .agentuity/app.js with AI Gateway routing patches applied
576
+ const { installExternalsAndBuild } = await import('../build/vite/server-bundler');
577
+ await installExternalsAndBuild({
578
+ rootDir,
579
+ dev: true, // DevMode: no minification, inline sourcemaps
580
+ logger,
581
+ });
582
+
583
+ // Generate metadata file (needed for eval ID lookup at runtime)
584
+ const { discoverAgents } = await import('../build/vite/agent-discovery');
585
+ const { discoverRoutes } = await import('../build/vite/route-discovery');
586
+ const { generateMetadata, writeMetadataFile } = await import(
587
+ '../build/vite/metadata-generator'
588
+ );
411
589
 
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
590
+ const srcDir = join(rootDir, 'src');
591
+
592
+ // Generate/update prompt files (non-blocking)
593
+ import('../build/vite/prompt-generator')
594
+ .then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
595
+ .catch((err) => logger.warn('Failed to generate prompt files: %s', err.message));
596
+ const agents = await discoverAgents(
597
+ srcDir,
598
+ project?.projectId ?? '',
599
+ deploymentId,
600
+ logger
419
601
  );
420
- previousMetadata = metadata;
421
- }
602
+ const { routes } = await discoverRoutes(
603
+ srcDir,
604
+ project?.projectId ?? '',
605
+ deploymentId,
606
+ logger
607
+ );
608
+
609
+ const metadata = await generateMetadata({
610
+ rootDir,
611
+ projectId: project?.projectId ?? '',
612
+ orgId: project?.orgId ?? '',
613
+ deploymentId,
614
+ agents,
615
+ routes,
616
+ dev: true,
617
+ logger,
618
+ });
619
+
620
+ writeMetadataFile(rootDir, metadata, true, logger);
621
+
622
+ // Sync metadata with backend (creates agents and evals in the database)
623
+ if (syncService && project?.projectId) {
624
+ await syncService.sync(
625
+ metadata,
626
+ previousMetadata,
627
+ project.projectId,
628
+ deploymentId
629
+ );
630
+ previousMetadata = metadata;
631
+ }
422
632
  },
423
633
  clearOnSuccess: true,
424
634
  });
@@ -600,7 +810,7 @@ export const command = createCommand({
600
810
  });
601
811
  break;
602
812
  case 'q':
603
- originalExit(0);
813
+ void cleanup(true, 0);
604
814
  break;
605
815
  default:
606
816
  process.stdout.write(data);
@@ -627,18 +837,8 @@ export const command = createCommand({
627
837
  // Restart triggered - cleanup and loop (Vite stays running)
628
838
  logger.debug('Restarting backend server...');
629
839
 
630
- // Kill gravity client (if running)
631
- if (gravityProcess) {
632
- try {
633
- gravityProcess.kill('SIGTERM');
634
- await new Promise((resolve) => setTimeout(resolve, 100));
635
- if (gravityProcess.exitCode === null) {
636
- gravityProcess.kill('SIGKILL');
637
- }
638
- } catch (err) {
639
- logger.debug('Error killing gravity process during restart: %s', err);
640
- }
641
- }
840
+ // Clean up Bun server and Gravity (Vite stays running)
841
+ await cleanupForRestart();
642
842
 
643
843
  // Brief pause before restart
644
844
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -647,17 +847,10 @@ export const command = createCommand({
647
847
  tui.warn('Waiting for file changes to retry...');
648
848
 
649
849
  // Cleanup on error (Vite stays running)
650
- if (gravityProcess) {
651
- try {
652
- gravityProcess.kill('SIGTERM');
653
- await new Promise((resolve) => setTimeout(resolve, 100));
654
- if (gravityProcess.exitCode === null) {
655
- gravityProcess.kill('SIGKILL');
656
- }
657
- } catch (err) {
658
- logger.debug('Error killing gravity process on error: %s', err);
659
- }
660
- }
850
+ await cleanupForRestart();
851
+
852
+ // Resume file watcher to detect changes for retry
853
+ fileWatcher.resume();
661
854
 
662
855
  // Wait for next restart trigger
663
856
  await new Promise<void>((resolve) => {
@@ -0,0 +1,82 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { semver } from 'bun';
4
+ import type { Logger } from '@agentuity/core';
5
+
6
+ const SKILLS_DIR = '.agents/skills/agentuity/cli';
7
+ const VERSION_FILE = 'version.txt';
8
+
9
+ interface SkillsCheckResult {
10
+ needsRegeneration: boolean;
11
+ reason?: 'missing' | 'outdated' | 'version-missing';
12
+ currentVersion?: string;
13
+ }
14
+
15
+ export async function checkSkillsVersion(
16
+ projectDir: string,
17
+ cliVersion: string
18
+ ): Promise<SkillsCheckResult> {
19
+ const skillsDir = join(projectDir, SKILLS_DIR);
20
+ const versionFile = join(skillsDir, VERSION_FILE);
21
+
22
+ if (!existsSync(skillsDir)) {
23
+ return { needsRegeneration: true, reason: 'missing' };
24
+ }
25
+
26
+ if (!existsSync(versionFile)) {
27
+ return { needsRegeneration: true, reason: 'version-missing' };
28
+ }
29
+
30
+ const currentVersion = (await Bun.file(versionFile).text()).trim();
31
+ if (!currentVersion) {
32
+ return { needsRegeneration: true, reason: 'version-missing' };
33
+ }
34
+
35
+ try {
36
+ const order = semver.order(currentVersion, cliVersion);
37
+ if (order < 0) {
38
+ return { needsRegeneration: true, reason: 'outdated', currentVersion };
39
+ }
40
+ } catch {
41
+ return { needsRegeneration: true, reason: 'outdated', currentVersion };
42
+ }
43
+
44
+ return { needsRegeneration: false, currentVersion };
45
+ }
46
+
47
+ export async function regenerateSkillsAsync(
48
+ projectDir: string,
49
+ cliVersion: string,
50
+ logger: Logger
51
+ ): Promise<void> {
52
+ const result = await checkSkillsVersion(projectDir, cliVersion);
53
+
54
+ if (!result.needsRegeneration) {
55
+ return;
56
+ }
57
+
58
+ const reasonMsg =
59
+ result.reason === 'missing'
60
+ ? 'Skills not found'
61
+ : result.reason === 'version-missing'
62
+ ? 'Skills version unknown'
63
+ : `Skills outdated (${result.currentVersion} < ${cliVersion})`;
64
+
65
+ logger.debug(`${reasonMsg}, regenerating...`);
66
+
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ const schema = (global as any).__CLI_SCHEMA__;
69
+ if (!schema) {
70
+ logger.debug('CLI schema not available, skipping skill regeneration');
71
+ return;
72
+ }
73
+
74
+ try {
75
+ const outputDir = join(projectDir, '.agents');
76
+ const { generateSkills } = await import('../ai/skills/generator');
77
+ await generateSkills(schema, outputDir, false);
78
+ logger.debug(`Skills regenerated to ${outputDir}/skills/agentuity/cli`);
79
+ } catch (error) {
80
+ logger.debug(`Failed to regenerate skills: ${error}`);
81
+ }
82
+ }
@@ -171,13 +171,13 @@ 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(
174
+ this.logger.debug(
175
175
  '[CLI EVAL SYNC] Agent "%s" has %d eval(s)',
176
176
  agent.name,
177
177
  agent.evals.length
178
178
  );
179
179
  for (const evalItem of agent.evals) {
180
- this.logger.info(
180
+ this.logger.debug(
181
181
  '[CLI EVAL SYNC] - %s (evalId: %s)',
182
182
  evalItem.name,
183
183
  evalItem.evalId
@@ -185,7 +185,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
185
185
  }
186
186
  }
187
187
  }
188
- this.logger.info('[CLI EVAL SYNC] Total current eval(s): %d', currentEvalCount);
188
+ this.logger.debug('[CLI EVAL SYNC] Total current eval(s): %d', currentEvalCount);
189
189
 
190
190
  // Get agents and evals to sync using shared diff logic
191
191
  const { create: agentsToCreate, delete: agentsToDelete } = getAgentsToSync(
@@ -280,7 +280,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
280
280
  );
281
281
 
282
282
  if (evals.length === 0 && evalsToDelete.length === 0) {
283
- this.logger.info('[CLI EVAL SYNC] No evals to sync, skipping');
283
+ this.logger.debug('[CLI EVAL SYNC] No evals to sync, skipping');
284
284
  return;
285
285
  }
286
286
 
@@ -290,9 +290,9 @@ class DevmodeSyncService implements IDevmodeSyncService {
290
290
  delete: evalsToDelete,
291
291
  };
292
292
 
293
- this.logger.info('[CLI EVAL SYNC] Sending payload to POST /cli/devmode/eval:');
293
+ this.logger.debug('[CLI EVAL SYNC] Sending payload to POST /cli/devmode/eval:');
294
294
  for (const evalItem of evals) {
295
- this.logger.info(
295
+ this.logger.debug(
296
296
  '[CLI EVAL SYNC] - %s (id: %s, evalId: %s)',
297
297
  evalItem.name,
298
298
  evalItem.id,
@@ -306,7 +306,7 @@ class DevmodeSyncService implements IDevmodeSyncService {
306
306
  payload,
307
307
  z.object({ success: z.boolean() })
308
308
  );
309
- this.logger.info('[CLI EVAL SYNC] Sync successful');
309
+ this.logger.debug('[CLI EVAL SYNC] Sync successful');
310
310
  } catch (error) {
311
311
  this.logger.error('[CLI EVAL SYNC] Sync failed: %s', error);
312
312
  throw error;
package/src/cmd/index.ts CHANGED
@@ -39,6 +39,7 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
39
39
  description: subcommand.description,
40
40
  aliases: subcommand.aliases,
41
41
  hidden: true,
42
+ skipSkill: true,
42
43
  requires: subcommand.requires,
43
44
  optional: subcommand.optional,
44
45
  schema: subcommand.schema,
@@ -12,6 +12,7 @@ const ProjectCreateResponseSchema = z.object({
12
12
  template: z.string().describe('Template used'),
13
13
  installed: z.boolean().describe('Whether dependencies were installed'),
14
14
  built: z.boolean().describe('Whether the project was built'),
15
+ domains: z.array(z.string()).optional().describe('Array of custom domains'),
15
16
  });
16
17
 
17
18
  export const createProjectSubcommand = createSubcommand({
@@ -40,6 +41,7 @@ export const createProjectSubcommand = createSubcommand({
40
41
  options: z.object({
41
42
  name: z.string().optional().describe('Project name'),
42
43
  dir: z.string().optional().describe('Directory to create the project in'),
44
+ domains: z.array(z.string()).optional().describe('Array of custom domains'),
43
45
  template: z.string().optional().describe('Template to use'),
44
46
  templateDir: z
45
47
  .string()
@@ -84,6 +86,7 @@ export const createProjectSubcommand = createSubcommand({
84
86
  await runCreateFlow({
85
87
  projectName: opts.name,
86
88
  dir: opts.dir,
89
+ domains: opts.domains,
87
90
  template: opts.template,
88
91
  templateDir: opts.templateDir,
89
92
  templateBranch: opts.templateBranch,