@agentuity/cli 0.0.100 → 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 (264) hide show
  1. package/AGENTS.md +19 -188
  2. package/bin/cli.ts +13 -6
  3. package/dist/api.d.ts +1 -0
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +1 -1
  6. package/dist/api.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +41 -12
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cmd/ai/index.d.ts.map +1 -1
  11. package/dist/cmd/ai/index.js +6 -1
  12. package/dist/cmd/ai/index.js.map +1 -1
  13. package/dist/cmd/ai/prompt/agent.d.ts +7 -0
  14. package/dist/cmd/ai/prompt/agent.d.ts.map +1 -1
  15. package/dist/cmd/ai/prompt/agent.js +12 -323
  16. package/dist/cmd/ai/prompt/agent.js.map +1 -1
  17. package/dist/cmd/ai/prompt/api.d.ts +7 -0
  18. package/dist/cmd/ai/prompt/api.d.ts.map +1 -1
  19. package/dist/cmd/ai/prompt/api.js +12 -260
  20. package/dist/cmd/ai/prompt/api.js.map +1 -1
  21. package/dist/cmd/ai/prompt/version.d.ts +35 -0
  22. package/dist/cmd/ai/prompt/version.d.ts.map +1 -0
  23. package/dist/cmd/ai/prompt/version.js +55 -0
  24. package/dist/cmd/ai/prompt/version.js.map +1 -0
  25. package/dist/cmd/ai/prompt/web.d.ts +7 -0
  26. package/dist/cmd/ai/prompt/web.d.ts.map +1 -1
  27. package/dist/cmd/ai/prompt/web.js +12 -283
  28. package/dist/cmd/ai/prompt/web.js.map +1 -1
  29. package/dist/cmd/ai/skills/generate.d.ts +3 -0
  30. package/dist/cmd/ai/skills/generate.d.ts.map +1 -0
  31. package/dist/cmd/ai/skills/generate.js +65 -0
  32. package/dist/cmd/ai/skills/generate.js.map +1 -0
  33. package/dist/cmd/ai/skills/generator.d.ts +4 -0
  34. package/dist/cmd/ai/skills/generator.d.ts.map +1 -0
  35. package/dist/cmd/ai/skills/generator.js +402 -0
  36. package/dist/cmd/ai/skills/generator.js.map +1 -0
  37. package/dist/cmd/ai/skills/index.d.ts +4 -0
  38. package/dist/cmd/ai/skills/index.d.ts.map +1 -0
  39. package/dist/cmd/ai/skills/index.js +21 -0
  40. package/dist/cmd/ai/skills/index.js.map +1 -0
  41. package/dist/cmd/auth/signup.d.ts.map +1 -1
  42. package/dist/cmd/auth/signup.js +1 -0
  43. package/dist/cmd/auth/signup.js.map +1 -1
  44. package/dist/cmd/build/ast.d.ts +2 -1
  45. package/dist/cmd/build/ast.d.ts.map +1 -1
  46. package/dist/cmd/build/ast.js +135 -47
  47. package/dist/cmd/build/ast.js.map +1 -1
  48. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  49. package/dist/cmd/build/entry-generator.js +255 -188
  50. package/dist/cmd/build/entry-generator.js.map +1 -1
  51. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  52. package/dist/cmd/build/vite/agent-discovery.js +103 -45
  53. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  54. package/dist/cmd/build/vite/bun-dev-server.d.ts +7 -1
  55. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  56. package/dist/cmd/build/vite/bun-dev-server.js +52 -26
  57. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  58. package/dist/cmd/build/vite/docs-generator.d.ts +13 -0
  59. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -0
  60. package/dist/cmd/build/vite/docs-generator.js +81 -0
  61. package/dist/cmd/build/vite/docs-generator.js.map +1 -0
  62. package/dist/cmd/build/vite/index.d.ts +3 -3
  63. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  64. package/dist/cmd/build/vite/index.js +9 -7
  65. package/dist/cmd/build/vite/index.js.map +1 -1
  66. package/dist/cmd/build/vite/lifecycle-generator.d.ts +1 -1
  67. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  68. package/dist/cmd/build/vite/lifecycle-generator.js +19 -5
  69. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  70. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  71. package/dist/cmd/build/vite/metadata-generator.js +203 -7
  72. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  73. package/dist/cmd/build/vite/prompt-generator.d.ts +23 -0
  74. package/dist/cmd/build/vite/prompt-generator.d.ts.map +1 -0
  75. package/dist/cmd/build/vite/prompt-generator.js +123 -0
  76. package/dist/cmd/build/vite/prompt-generator.js.map +1 -0
  77. package/dist/cmd/build/vite/registry-generator.d.ts +3 -3
  78. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  79. package/dist/cmd/build/vite/registry-generator.js +644 -103
  80. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  81. package/dist/cmd/build/vite/route-discovery.d.ts +4 -0
  82. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  83. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  84. package/dist/cmd/build/vite/server-bundler.d.ts +4 -0
  85. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  86. package/dist/cmd/build/vite/server-bundler.js +63 -17
  87. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  88. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  89. package/dist/cmd/build/vite/vite-asset-server-config.js +4 -0
  90. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  91. package/dist/cmd/build/vite/vite-builder.d.ts +1 -1
  92. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  93. package/dist/cmd/build/vite/vite-builder.js +118 -96
  94. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  95. package/dist/cmd/build/vite-bundler.js +6 -6
  96. package/dist/cmd/build/vite-bundler.js.map +1 -1
  97. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  98. package/dist/cmd/cloud/deploy.js +89 -32
  99. package/dist/cmd/cloud/deploy.js.map +1 -1
  100. package/dist/cmd/cloud/keyvalue/create-namespace.d.ts.map +1 -1
  101. package/dist/cmd/cloud/keyvalue/create-namespace.js +3 -1
  102. package/dist/cmd/cloud/keyvalue/create-namespace.js.map +1 -1
  103. package/dist/cmd/cloud/keyvalue/delete-namespace.d.ts.map +1 -1
  104. package/dist/cmd/cloud/keyvalue/delete-namespace.js +3 -1
  105. package/dist/cmd/cloud/keyvalue/delete-namespace.js.map +1 -1
  106. package/dist/cmd/cloud/keyvalue/delete.d.ts.map +1 -1
  107. package/dist/cmd/cloud/keyvalue/delete.js +3 -1
  108. package/dist/cmd/cloud/keyvalue/delete.js.map +1 -1
  109. package/dist/cmd/cloud/keyvalue/set.d.ts.map +1 -1
  110. package/dist/cmd/cloud/keyvalue/set.js +4 -2
  111. package/dist/cmd/cloud/keyvalue/set.js.map +1 -1
  112. package/dist/cmd/cloud/stream/get.d.ts.map +1 -1
  113. package/dist/cmd/cloud/stream/get.js +2 -13
  114. package/dist/cmd/cloud/stream/get.js.map +1 -1
  115. package/dist/cmd/cloud/vector/delete-namespace.d.ts +3 -0
  116. package/dist/cmd/cloud/vector/delete-namespace.d.ts.map +1 -0
  117. package/dist/cmd/cloud/vector/delete-namespace.js +77 -0
  118. package/dist/cmd/cloud/vector/delete-namespace.js.map +1 -0
  119. package/dist/cmd/cloud/vector/index.d.ts.map +1 -1
  120. package/dist/cmd/cloud/vector/index.js +21 -4
  121. package/dist/cmd/cloud/vector/index.js.map +1 -1
  122. package/dist/cmd/cloud/vector/list-namespaces.d.ts +3 -0
  123. package/dist/cmd/cloud/vector/list-namespaces.d.ts.map +1 -0
  124. package/dist/cmd/cloud/vector/list-namespaces.js +42 -0
  125. package/dist/cmd/cloud/vector/list-namespaces.js.map +1 -0
  126. package/dist/cmd/cloud/vector/stats.d.ts +3 -0
  127. package/dist/cmd/cloud/vector/stats.d.ts.map +1 -0
  128. package/dist/cmd/cloud/vector/stats.js +142 -0
  129. package/dist/cmd/cloud/vector/stats.js.map +1 -0
  130. package/dist/cmd/cloud/vector/upsert.d.ts +3 -0
  131. package/dist/cmd/cloud/vector/upsert.d.ts.map +1 -0
  132. package/dist/cmd/cloud/vector/upsert.js +192 -0
  133. package/dist/cmd/cloud/vector/upsert.js.map +1 -0
  134. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  135. package/dist/cmd/dev/file-watcher.js +94 -33
  136. package/dist/cmd/dev/file-watcher.js.map +1 -1
  137. package/dist/cmd/dev/index.d.ts.map +1 -1
  138. package/dist/cmd/dev/index.js +298 -61
  139. package/dist/cmd/dev/index.js.map +1 -1
  140. package/dist/cmd/dev/skills.d.ts +10 -0
  141. package/dist/cmd/dev/skills.d.ts.map +1 -0
  142. package/dist/cmd/dev/skills.js +57 -0
  143. package/dist/cmd/dev/skills.js.map +1 -0
  144. package/dist/cmd/dev/sync.d.ts.map +1 -1
  145. package/dist/cmd/dev/sync.js +19 -3
  146. package/dist/cmd/dev/sync.js.map +1 -1
  147. package/dist/cmd/index.d.ts.map +1 -1
  148. package/dist/cmd/index.js +1 -0
  149. package/dist/cmd/index.js.map +1 -1
  150. package/dist/cmd/project/create.d.ts.map +1 -1
  151. package/dist/cmd/project/create.js +3 -0
  152. package/dist/cmd/project/create.js.map +1 -1
  153. package/dist/cmd/project/template-flow.d.ts +1 -0
  154. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  155. package/dist/cmd/project/template-flow.js +30 -5
  156. package/dist/cmd/project/template-flow.js.map +1 -1
  157. package/dist/cmd/setup/index.d.ts.map +1 -1
  158. package/dist/cmd/setup/index.js +1 -0
  159. package/dist/cmd/setup/index.js.map +1 -1
  160. package/dist/cmd/upgrade/index.d.ts +15 -0
  161. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  162. package/dist/cmd/upgrade/index.js +59 -4
  163. package/dist/cmd/upgrade/index.js.map +1 -1
  164. package/dist/config.d.ts.map +1 -1
  165. package/dist/config.js +8 -0
  166. package/dist/config.js.map +1 -1
  167. package/dist/domain.d.ts +45 -0
  168. package/dist/domain.d.ts.map +1 -0
  169. package/dist/domain.js +200 -0
  170. package/dist/domain.js.map +1 -0
  171. package/dist/index.d.ts +0 -1
  172. package/dist/index.d.ts.map +1 -1
  173. package/dist/index.js +0 -1
  174. package/dist/index.js.map +1 -1
  175. package/dist/schema-generator.d.ts +2 -0
  176. package/dist/schema-generator.d.ts.map +1 -1
  177. package/dist/schema-generator.js +18 -0
  178. package/dist/schema-generator.js.map +1 -1
  179. package/dist/steps.d.ts +1 -1
  180. package/dist/steps.d.ts.map +1 -1
  181. package/dist/steps.js +16 -5
  182. package/dist/steps.js.map +1 -1
  183. package/dist/tui/prompt.d.ts +1 -2
  184. package/dist/tui/prompt.d.ts.map +1 -1
  185. package/dist/tui/prompt.js +8 -4
  186. package/dist/tui/prompt.js.map +1 -1
  187. package/dist/tui.d.ts +16 -0
  188. package/dist/tui.d.ts.map +1 -1
  189. package/dist/tui.js +23 -2
  190. package/dist/tui.js.map +1 -1
  191. package/dist/types.d.ts +9 -2
  192. package/dist/types.d.ts.map +1 -1
  193. package/dist/types.js +3 -3
  194. package/dist/types.js.map +1 -1
  195. package/package.json +5 -8
  196. package/src/api.ts +1 -1
  197. package/src/cli.ts +47 -12
  198. package/src/cmd/ai/index.ts +6 -1
  199. package/src/cmd/ai/prompt/agent.md +306 -0
  200. package/src/cmd/ai/prompt/agent.ts +12 -322
  201. package/src/cmd/ai/prompt/api.md +360 -0
  202. package/src/cmd/ai/prompt/api.ts +13 -260
  203. package/src/cmd/ai/prompt/version.ts +61 -0
  204. package/src/cmd/ai/prompt/web.md +509 -0
  205. package/src/cmd/ai/prompt/web.ts +12 -282
  206. package/src/cmd/ai/skills/generate.ts +75 -0
  207. package/src/cmd/ai/skills/generator.ts +519 -0
  208. package/src/cmd/ai/skills/index.ts +23 -0
  209. package/src/cmd/auth/signup.ts +1 -0
  210. package/src/cmd/build/ast.ts +161 -48
  211. package/src/cmd/build/entry-generator.ts +258 -187
  212. package/src/cmd/build/vite/agent-discovery.ts +151 -58
  213. package/src/cmd/build/vite/bun-dev-server.ts +57 -27
  214. package/src/cmd/build/vite/docs-generator.ts +87 -0
  215. package/src/cmd/build/vite/index.ts +9 -7
  216. package/src/cmd/build/vite/lifecycle-generator.ts +19 -5
  217. package/src/cmd/build/vite/metadata-generator.ts +251 -7
  218. package/src/cmd/build/vite/prompt-generator.ts +169 -0
  219. package/src/cmd/build/vite/registry-generator.ts +750 -108
  220. package/src/cmd/build/vite/route-discovery.ts +4 -0
  221. package/src/cmd/build/vite/server-bundler.ts +73 -23
  222. package/src/cmd/build/vite/vite-asset-server-config.ts +5 -0
  223. package/src/cmd/build/vite/vite-builder.ts +134 -100
  224. package/src/cmd/build/vite-bundler.ts +6 -6
  225. package/src/cmd/cloud/deploy.ts +114 -36
  226. package/src/cmd/cloud/keyvalue/create-namespace.ts +3 -1
  227. package/src/cmd/cloud/keyvalue/delete-namespace.ts +3 -1
  228. package/src/cmd/cloud/keyvalue/delete.ts +3 -1
  229. package/src/cmd/cloud/keyvalue/set.ts +4 -2
  230. package/src/cmd/cloud/stream/get.ts +2 -9
  231. package/src/cmd/cloud/vector/delete-namespace.ts +89 -0
  232. package/src/cmd/cloud/vector/index.ts +21 -4
  233. package/src/cmd/cloud/vector/list-namespaces.ts +46 -0
  234. package/src/cmd/cloud/vector/stats.ts +160 -0
  235. package/src/cmd/cloud/vector/upsert.ts +216 -0
  236. package/src/cmd/dev/file-watcher.ts +109 -34
  237. package/src/cmd/dev/index.ts +364 -60
  238. package/src/cmd/dev/skills.ts +82 -0
  239. package/src/cmd/dev/sync.ts +41 -6
  240. package/src/cmd/index.ts +1 -0
  241. package/src/cmd/project/create.ts +3 -0
  242. package/src/cmd/project/template-flow.ts +37 -5
  243. package/src/cmd/setup/index.ts +1 -0
  244. package/src/cmd/upgrade/index.ts +68 -4
  245. package/src/config.ts +9 -0
  246. package/src/domain.ts +273 -0
  247. package/src/index.ts +0 -5
  248. package/src/runtime-bootstrap.md +1 -1
  249. package/src/schema-generator.ts +23 -0
  250. package/src/steps.ts +16 -5
  251. package/src/tui/prompt.ts +11 -5
  252. package/src/tui.ts +21 -2
  253. package/src/types/md.d.ts +8 -0
  254. package/src/types.ts +12 -3
  255. package/dist/cmd/cloud/domain.d.ts +0 -17
  256. package/dist/cmd/cloud/domain.d.ts.map +0 -1
  257. package/dist/cmd/cloud/domain.js +0 -79
  258. package/dist/cmd/cloud/domain.js.map +0 -1
  259. package/dist/runtime-bootstrap.d.ts +0 -56
  260. package/dist/runtime-bootstrap.d.ts.map +0 -1
  261. package/dist/runtime-bootstrap.js +0 -95
  262. package/dist/runtime-bootstrap.js.map +0 -1
  263. package/src/cmd/cloud/domain.ts +0 -100
  264. package/src/runtime-bootstrap.ts +0 -131
@@ -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';
@@ -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) {
@@ -99,6 +175,13 @@ export const command = createCommand({
99
175
 
100
176
  const interactive = !shouldDisableInteractive(opts.interactive);
101
177
 
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ let originalExit = (globalThis as any).AGENTUITY_PROCESS_EXIT;
180
+
181
+ if (!originalExit) {
182
+ originalExit = process.exit.bind(process);
183
+ }
184
+
102
185
  for (const filename of mustHaves) {
103
186
  if (!existsSync(filename)) {
104
187
  missing.push(filename);
@@ -110,17 +193,28 @@ export const command = createCommand({
110
193
  for (const filename of missing) {
111
194
  tui.bullet(`Missing ${filename}`);
112
195
  }
113
- internalExit(1);
196
+ originalExit(1);
114
197
  }
115
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
+
116
203
  // Setup devmode and gravity (if using public URL)
117
204
  const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
118
205
  const apiClient = auth ? new APIClient(getAPIBaseURL(config), logger, config) : null;
119
- createDevmodeSyncService({
120
- logger,
121
- apiClient,
122
- mock: useMockService,
123
- });
206
+ const syncService = apiClient
207
+ ? createDevmodeSyncService({
208
+ logger,
209
+ apiClient,
210
+ mock: useMockService,
211
+ })
212
+ : null;
213
+
214
+ // Track previous metadata for sync diffing
215
+ let previousMetadata:
216
+ | Awaited<ReturnType<typeof import('../build/vite/metadata-generator').generateMetadata>>
217
+ | undefined;
124
218
 
125
219
  let devmode: DevmodeResponse | undefined;
126
220
  let gravityBin: string | undefined;
@@ -222,10 +316,16 @@ export const command = createCommand({
222
316
  centerTitle: false,
223
317
  });
224
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
+
225
325
  // Start Vite asset server ONCE before restart loop
226
326
  // Vite handles frontend HMR independently and stays running across backend restarts
227
- let vitePort: number;
228
327
  let viteServer: ServerLike | null = null;
328
+ let vitePort: number;
229
329
 
230
330
  try {
231
331
  logger.debug('Starting Vite asset server...');
@@ -241,13 +341,15 @@ export const command = createCommand({
241
341
  );
242
342
  } catch (error) {
243
343
  tui.error(`Failed to start Vite asset server: ${error}`);
244
- internalExit(1);
344
+ originalExit(1);
345
+ return;
245
346
  }
246
347
 
247
348
  // Restart loop - allows BACKEND server to restart on file changes
248
349
  // Vite stays running and handles frontend changes via HMR
249
350
  let shouldRestart = false;
250
351
  let gravityProcess: ProcessLike | null = null;
352
+ let stdinListenerRegistered = false; // Track if stdin listener is already registered
251
353
 
252
354
  const restartServer = () => {
253
355
  shouldRestart = true;
@@ -267,16 +369,36 @@ export const command = createCommand({
267
369
  // Start file watcher (will be paused during builds)
268
370
  fileWatcher.start();
269
371
 
270
- // Setup signal handlers once before the loop
271
- const cleanup = async () => {
272
- 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
+ }
273
389
 
274
- // Stop file watcher
275
- 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
+ }
276
396
 
277
- // Close Vite asset server first
278
- if (viteServer) {
279
- 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);
280
402
  }
281
403
 
282
404
  // Kill gravity client with SIGTERM first, then SIGKILL as fallback
@@ -284,26 +406,114 @@ export const command = createCommand({
284
406
  try {
285
407
  gravityProcess.kill('SIGTERM');
286
408
  // Give it a moment to gracefully shutdown
287
- await new Promise((resolve) => setTimeout(resolve, 100));
409
+ await new Promise((resolve) => setTimeout(resolve, 150));
288
410
  if (gravityProcess.exitCode === null) {
289
411
  gravityProcess.kill('SIGKILL');
290
412
  }
291
413
  } catch (err) {
292
414
  logger.debug('Error killing gravity process: %s', err);
415
+ } finally {
416
+ gravityProcess = null;
293
417
  }
294
418
  }
295
419
 
296
- internalExit(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
+ }
297
437
  };
298
438
 
299
- process.on('SIGINT', cleanup);
300
- process.on('SIGTERM', cleanup);
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
+ }
466
+ };
301
467
 
302
- // Ensure Vite and gravity are always killed on exit (even if cleanup is bypassed)
468
+ // SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
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
+ });
483
+
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)
303
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
+
304
516
  // Close Vite server synchronously if possible
305
- // Note: Vite's close() is async, but we can't await in 'exit' handler
306
- // Most Vite implementations handle sync close gracefully
307
517
  if (viteServer) {
308
518
  try {
309
519
  viteServer.close();
@@ -312,10 +522,12 @@ export const command = createCommand({
312
522
  }
313
523
  }
314
524
 
315
- // Kill gravity client with SIGKILL for immediate termination
316
- 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) {
317
529
  try {
318
- gravityProcess.kill('SIGKILL');
530
+ server.stop(true);
319
531
  } catch {
320
532
  // Ignore errors during exit cleanup
321
533
  }
@@ -329,10 +541,26 @@ export const command = createCommand({
329
541
  fileWatcher.pause();
330
542
 
331
543
  try {
332
- // Generate entry file for Vite before starting dev server
544
+ // Generate entry file and bundle for dev server (with LLM patches)
333
545
  await tui.spinner({
334
- message: 'Generating entry file',
546
+ message: 'Building dev bundle',
335
547
  callback: async () => {
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
+ }
561
+
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
336
564
  const { generateEntryFile } = await import('../build/entry-generator');
337
565
  await generateEntryFile({
338
566
  rootDir,
@@ -340,12 +568,72 @@ export const command = createCommand({
340
568
  deploymentId,
341
569
  logger,
342
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
+ );
589
+
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
601
+ );
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,
343
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
+ }
344
632
  },
345
633
  clearOnSuccess: true,
346
634
  });
347
635
  } catch (error) {
348
- tui.error(`Failed to generate entry file: ${error}`);
636
+ tui.error(`Failed to build dev bundle: ${error}`);
349
637
  tui.warn('Waiting for file changes to retry...');
350
638
 
351
639
  // Resume watcher to detect changes for retry
@@ -364,6 +652,34 @@ export const command = createCommand({
364
652
  }
365
653
 
366
654
  try {
655
+ // Set environment variables for LLM provider patches BEFORE starting server
656
+ // These must be set so the bundled patches can route LLM calls through AI Gateway
657
+ const serviceUrls = getServiceUrls(project?.region);
658
+
659
+ process.env.AGENTUITY_SDK_DEV_MODE = 'true';
660
+ process.env.AGENTUITY_ENV = 'development';
661
+ process.env.NODE_ENV = 'development';
662
+ if (project?.region) {
663
+ process.env.AGENTUITY_REGION = project.region;
664
+ }
665
+ process.env.PORT = String(opts.port);
666
+ process.env.AGENTUITY_PORT = process.env.PORT;
667
+
668
+ if (project) {
669
+ process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
670
+ process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
671
+ process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
672
+ process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
673
+ process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
674
+ process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
675
+ process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
676
+ }
677
+
678
+ // Set Vite port for asset proxying in bundled app
679
+ process.env.VITE_PORT = String(vitePort);
680
+
681
+ logger.debug('Set VITE_PORT=%s for asset proxying', process.env.VITE_PORT);
682
+
367
683
  // Start Bun dev server (Vite already running, just start backend)
368
684
  await startBunDevServer({
369
685
  rootDir,
@@ -375,8 +691,6 @@ export const command = createCommand({
375
691
  vitePort, // Pass port of already-running Vite server
376
692
  });
377
693
 
378
- // Note: Bun server runs in-process, no separate app process needed
379
-
380
694
  // Wait for app.ts to finish loading (Vite is ready but app may still be initializing)
381
695
  // Give it 2 seconds to ensure app initialization completes
382
696
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -454,8 +768,14 @@ export const command = createCommand({
454
768
  // TODO: Integrate sync service with Vite's buildStart/buildEnd hooks
455
769
  // The sync service will be called when metadata changes are detected
456
770
 
457
- // Handle keyboard shortcuts
458
- if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
771
+ // Handle keyboard shortcuts - only register listener once
772
+ if (
773
+ interactive &&
774
+ process.stdin.isTTY &&
775
+ process.stdout.isTTY &&
776
+ !stdinListenerRegistered
777
+ ) {
778
+ stdinListenerRegistered = true;
459
779
  process.stdin.setRawMode(true);
460
780
  process.stdin.resume();
461
781
  process.stdin.setEncoding('utf8');
@@ -470,9 +790,10 @@ export const command = createCommand({
470
790
  process.stdin.on('data', (data) => {
471
791
  const key = data.toString();
472
792
 
473
- // Handle Ctrl+C
793
+ // Handle Ctrl+C - send SIGINT to trigger graceful shutdown
474
794
  if (key === '\u0003') {
475
- internalExit(0);
795
+ process.kill(process.pid, 'SIGINT');
796
+ return;
476
797
  }
477
798
 
478
799
  switch (key) {
@@ -489,7 +810,7 @@ export const command = createCommand({
489
810
  });
490
811
  break;
491
812
  case 'q':
492
- internalExit(0);
813
+ void cleanup(true, 0);
493
814
  break;
494
815
  default:
495
816
  process.stdout.write(data);
@@ -516,18 +837,8 @@ export const command = createCommand({
516
837
  // Restart triggered - cleanup and loop (Vite stays running)
517
838
  logger.debug('Restarting backend server...');
518
839
 
519
- // Kill gravity client (if running)
520
- if (gravityProcess) {
521
- try {
522
- gravityProcess.kill('SIGTERM');
523
- await new Promise((resolve) => setTimeout(resolve, 100));
524
- if (gravityProcess.exitCode === null) {
525
- gravityProcess.kill('SIGKILL');
526
- }
527
- } catch (err) {
528
- logger.debug('Error killing gravity process during restart: %s', err);
529
- }
530
- }
840
+ // Clean up Bun server and Gravity (Vite stays running)
841
+ await cleanupForRestart();
531
842
 
532
843
  // Brief pause before restart
533
844
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -536,17 +847,10 @@ export const command = createCommand({
536
847
  tui.warn('Waiting for file changes to retry...');
537
848
 
538
849
  // Cleanup on error (Vite stays running)
539
- if (gravityProcess) {
540
- try {
541
- gravityProcess.kill('SIGTERM');
542
- await new Promise((resolve) => setTimeout(resolve, 100));
543
- if (gravityProcess.exitCode === null) {
544
- gravityProcess.kill('SIGKILL');
545
- }
546
- } catch (err) {
547
- logger.debug('Error killing gravity process on error: %s', err);
548
- }
549
- }
850
+ await cleanupForRestart();
851
+
852
+ // Resume file watcher to detect changes for retry
853
+ fileWatcher.resume();
550
854
 
551
855
  // Wait for next restart trigger
552
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
+ }