@agentuity/cli 0.0.94 → 0.0.96

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 (277) hide show
  1. package/AGENTS.md +54 -0
  2. package/dist/auth.d.ts +1 -1
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +8 -5
  5. package/dist/auth.js.map +1 -1
  6. package/dist/banner.js +1 -1
  7. package/dist/banner.js.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +190 -27
  10. package/dist/cli.js.map +1 -1
  11. package/dist/cmd/auth/signup.js +1 -1
  12. package/dist/cmd/auth/signup.js.map +1 -1
  13. package/dist/cmd/build/ast.d.ts.map +1 -1
  14. package/dist/cmd/build/ast.js +7 -0
  15. package/dist/cmd/build/ast.js.map +1 -1
  16. package/dist/cmd/build/entry-generator.d.ts +20 -0
  17. package/dist/cmd/build/entry-generator.d.ts.map +1 -0
  18. package/dist/cmd/build/entry-generator.js +366 -0
  19. package/dist/cmd/build/entry-generator.js.map +1 -0
  20. package/dist/cmd/build/index.d.ts.map +1 -1
  21. package/dist/cmd/build/index.js +5 -23
  22. package/dist/cmd/build/index.js.map +1 -1
  23. package/dist/cmd/build/vite/agent-discovery.d.ts +33 -0
  24. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -0
  25. package/dist/cmd/build/vite/agent-discovery.js +297 -0
  26. package/dist/cmd/build/vite/agent-discovery.js.map +1 -0
  27. package/dist/cmd/build/vite/browser-env-plugin.d.ts +9 -0
  28. package/dist/cmd/build/vite/browser-env-plugin.d.ts.map +1 -0
  29. package/dist/cmd/build/vite/browser-env-plugin.js +28 -0
  30. package/dist/cmd/build/vite/browser-env-plugin.js.map +1 -0
  31. package/dist/cmd/build/vite/bun-dev-server.d.ts +29 -0
  32. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -0
  33. package/dist/cmd/build/vite/bun-dev-server.js +54 -0
  34. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -0
  35. package/dist/cmd/build/vite/config-loader.d.ts +23 -0
  36. package/dist/cmd/build/vite/config-loader.d.ts.map +1 -0
  37. package/dist/cmd/build/vite/config-loader.js +50 -0
  38. package/dist/cmd/build/vite/config-loader.js.map +1 -0
  39. package/dist/cmd/build/vite/index.d.ts +26 -0
  40. package/dist/cmd/build/vite/index.d.ts.map +1 -0
  41. package/dist/cmd/build/vite/index.js +127 -0
  42. package/dist/cmd/build/vite/index.js.map +1 -0
  43. package/dist/cmd/build/vite/lifecycle-generator.d.ts +11 -0
  44. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -0
  45. package/dist/cmd/build/vite/lifecycle-generator.js +35 -0
  46. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -0
  47. package/dist/cmd/build/vite/metadata-generator.d.ts +32 -0
  48. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -0
  49. package/dist/cmd/build/vite/metadata-generator.js +489 -0
  50. package/dist/cmd/build/vite/metadata-generator.js.map +1 -0
  51. package/dist/cmd/build/vite/patch-plugin.d.ts +21 -0
  52. package/dist/cmd/build/vite/patch-plugin.d.ts.map +1 -0
  53. package/dist/cmd/build/vite/patch-plugin.js +70 -0
  54. package/dist/cmd/build/vite/patch-plugin.js.map +1 -0
  55. package/dist/cmd/build/vite/registry-generator.d.ts +19 -0
  56. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -0
  57. package/dist/cmd/build/{route-registry.js → vite/registry-generator.js} +126 -48
  58. package/dist/cmd/build/vite/registry-generator.js.map +1 -0
  59. package/dist/cmd/build/vite/route-discovery.d.ts +58 -0
  60. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -0
  61. package/dist/cmd/build/vite/route-discovery.js +125 -0
  62. package/dist/cmd/build/vite/route-discovery.js.map +1 -0
  63. package/dist/cmd/build/vite/server-bundler.d.ts +16 -0
  64. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -0
  65. package/dist/cmd/build/vite/server-bundler.js +194 -0
  66. package/dist/cmd/build/vite/server-bundler.js.map +1 -0
  67. package/dist/cmd/build/vite/vite-asset-server-config.d.ts +19 -0
  68. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -0
  69. package/dist/cmd/build/vite/vite-asset-server-config.js +105 -0
  70. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -0
  71. package/dist/cmd/build/vite/vite-asset-server.d.ts +23 -0
  72. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -0
  73. package/dist/cmd/build/vite/vite-asset-server.js +40 -0
  74. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -0
  75. package/dist/cmd/build/vite/vite-builder.d.ts +44 -0
  76. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -0
  77. package/dist/cmd/build/vite/vite-builder.js +232 -0
  78. package/dist/cmd/build/vite/vite-builder.js.map +1 -0
  79. package/dist/cmd/build/vite/workbench-generator.d.ts +10 -0
  80. package/dist/cmd/build/vite/workbench-generator.d.ts.map +1 -0
  81. package/dist/cmd/build/vite/workbench-generator.js +135 -0
  82. package/dist/cmd/build/vite/workbench-generator.js.map +1 -0
  83. package/dist/cmd/build/vite-bundler.d.ts +23 -0
  84. package/dist/cmd/build/vite-bundler.d.ts.map +1 -0
  85. package/dist/cmd/build/vite-bundler.js +79 -0
  86. package/dist/cmd/build/vite-bundler.js.map +1 -0
  87. package/dist/cmd/cloud/agent/get.d.ts.map +1 -1
  88. package/dist/cmd/cloud/agent/get.js +1 -0
  89. package/dist/cmd/cloud/agent/get.js.map +1 -1
  90. package/dist/cmd/cloud/agent/list.d.ts.map +1 -1
  91. package/dist/cmd/cloud/agent/list.js +1 -0
  92. package/dist/cmd/cloud/agent/list.js.map +1 -1
  93. package/dist/cmd/cloud/db/get.d.ts.map +1 -1
  94. package/dist/cmd/cloud/db/get.js +1 -0
  95. package/dist/cmd/cloud/db/get.js.map +1 -1
  96. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  97. package/dist/cmd/cloud/db/list.js +1 -0
  98. package/dist/cmd/cloud/db/list.js.map +1 -1
  99. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  100. package/dist/cmd/cloud/deploy.js +152 -128
  101. package/dist/cmd/cloud/deploy.js.map +1 -1
  102. package/dist/cmd/cloud/deployment/list.d.ts.map +1 -1
  103. package/dist/cmd/cloud/deployment/list.js +4 -0
  104. package/dist/cmd/cloud/deployment/list.js.map +1 -1
  105. package/dist/cmd/cloud/env/list.d.ts.map +1 -1
  106. package/dist/cmd/cloud/env/list.js +1 -0
  107. package/dist/cmd/cloud/env/list.js.map +1 -1
  108. package/dist/cmd/cloud/keyvalue/get.d.ts.map +1 -1
  109. package/dist/cmd/cloud/keyvalue/get.js +1 -0
  110. package/dist/cmd/cloud/keyvalue/get.js.map +1 -1
  111. package/dist/cmd/cloud/keyvalue/keys.d.ts.map +1 -1
  112. package/dist/cmd/cloud/keyvalue/keys.js +1 -0
  113. package/dist/cmd/cloud/keyvalue/keys.js.map +1 -1
  114. package/dist/cmd/cloud/keyvalue/list-namespaces.d.ts.map +1 -1
  115. package/dist/cmd/cloud/keyvalue/list-namespaces.js +1 -0
  116. package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
  117. package/dist/cmd/cloud/keyvalue/stats.d.ts.map +1 -1
  118. package/dist/cmd/cloud/keyvalue/stats.js +1 -0
  119. package/dist/cmd/cloud/keyvalue/stats.js.map +1 -1
  120. package/dist/cmd/cloud/secret/list.d.ts.map +1 -1
  121. package/dist/cmd/cloud/secret/list.js +1 -0
  122. package/dist/cmd/cloud/secret/list.js.map +1 -1
  123. package/dist/cmd/cloud/session/list.d.ts.map +1 -1
  124. package/dist/cmd/cloud/session/list.js +4 -0
  125. package/dist/cmd/cloud/session/list.js.map +1 -1
  126. package/dist/cmd/cloud/storage/get.d.ts.map +1 -1
  127. package/dist/cmd/cloud/storage/get.js +1 -0
  128. package/dist/cmd/cloud/storage/get.js.map +1 -1
  129. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  130. package/dist/cmd/cloud/storage/list.js +3 -0
  131. package/dist/cmd/cloud/storage/list.js.map +1 -1
  132. package/dist/cmd/cloud/stream/get.d.ts.map +1 -1
  133. package/dist/cmd/cloud/stream/get.js +1 -0
  134. package/dist/cmd/cloud/stream/get.js.map +1 -1
  135. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  136. package/dist/cmd/cloud/stream/list.js +1 -0
  137. package/dist/cmd/cloud/stream/list.js.map +1 -1
  138. package/dist/cmd/cloud/vector/get.d.ts.map +1 -1
  139. package/dist/cmd/cloud/vector/get.js +1 -0
  140. package/dist/cmd/cloud/vector/get.js.map +1 -1
  141. package/dist/cmd/cloud/vector/search.d.ts.map +1 -1
  142. package/dist/cmd/cloud/vector/search.js +1 -0
  143. package/dist/cmd/cloud/vector/search.js.map +1 -1
  144. package/dist/cmd/dev/index.d.ts.map +1 -1
  145. package/dist/cmd/dev/index.js +291 -751
  146. package/dist/cmd/dev/index.js.map +1 -1
  147. package/dist/cmd/dev/sync.d.ts +1 -1
  148. package/dist/cmd/dev/sync.d.ts.map +1 -1
  149. package/dist/cmd/dev/sync.js +3 -0
  150. package/dist/cmd/dev/sync.js.map +1 -1
  151. package/dist/cmd/profile/show.d.ts.map +1 -1
  152. package/dist/cmd/profile/show.js +3 -4
  153. package/dist/cmd/profile/show.js.map +1 -1
  154. package/dist/cmd/project/create.d.ts.map +1 -1
  155. package/dist/cmd/project/create.js +3 -4
  156. package/dist/cmd/project/create.js.map +1 -1
  157. package/dist/cmd/project/list.d.ts.map +1 -1
  158. package/dist/cmd/project/list.js +1 -0
  159. package/dist/cmd/project/list.js.map +1 -1
  160. package/dist/cmd/project/show.d.ts.map +1 -1
  161. package/dist/cmd/project/show.js +1 -0
  162. package/dist/cmd/project/show.js.map +1 -1
  163. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  164. package/dist/cmd/upgrade/index.js +5 -3
  165. package/dist/cmd/upgrade/index.js.map +1 -1
  166. package/dist/config.d.ts +1 -1
  167. package/dist/config.d.ts.map +1 -1
  168. package/dist/config.js +29 -11
  169. package/dist/config.js.map +1 -1
  170. package/dist/index.d.ts +1 -1
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/runtime-bootstrap.d.ts +3 -2
  174. package/dist/runtime-bootstrap.d.ts.map +1 -1
  175. package/dist/runtime-bootstrap.js +7 -2
  176. package/dist/runtime-bootstrap.js.map +1 -1
  177. package/dist/schemas/deploy.d.ts +1 -1
  178. package/dist/types.d.ts +40 -1
  179. package/dist/types.d.ts.map +1 -1
  180. package/dist/types.js.map +1 -1
  181. package/dist/utils/bun-version-checker.d.ts +11 -0
  182. package/dist/utils/bun-version-checker.d.ts.map +1 -0
  183. package/dist/utils/bun-version-checker.js +56 -0
  184. package/dist/utils/bun-version-checker.js.map +1 -0
  185. package/dist/version-check.d.ts.map +1 -1
  186. package/dist/version-check.js +5 -2
  187. package/dist/version-check.js.map +1 -1
  188. package/package.json +10 -3
  189. package/src/auth.ts +9 -5
  190. package/src/banner.ts +1 -1
  191. package/src/cli.ts +228 -29
  192. package/src/cmd/auth/signup.ts +1 -1
  193. package/src/cmd/build/ast.ts +7 -0
  194. package/src/cmd/build/entry-generator.ts +404 -0
  195. package/src/cmd/build/index.ts +7 -28
  196. package/src/cmd/build/vite/agent-discovery.ts +467 -0
  197. package/src/cmd/build/vite/browser-env-plugin.ts +34 -0
  198. package/src/cmd/build/vite/bun-dev-server.ts +78 -0
  199. package/src/cmd/build/vite/config-loader.ts +70 -0
  200. package/src/cmd/build/vite/index.ts +166 -0
  201. package/src/cmd/build/vite/lifecycle-generator.ts +43 -0
  202. package/src/cmd/build/vite/metadata-generator.ts +602 -0
  203. package/src/cmd/build/vite/patch-plugin.ts +88 -0
  204. package/src/cmd/build/vite/registry-generator.ts +288 -0
  205. package/src/cmd/build/vite/route-discovery.ts +186 -0
  206. package/src/cmd/build/vite/server-bundler.ts +258 -0
  207. package/src/cmd/build/vite/vite-asset-server-config.ts +134 -0
  208. package/src/cmd/build/vite/vite-asset-server.ts +63 -0
  209. package/src/cmd/build/vite/vite-builder.ts +284 -0
  210. package/src/cmd/build/vite/workbench-generator.ts +152 -0
  211. package/src/cmd/build/vite-bundler.ts +110 -0
  212. package/src/cmd/cloud/agent/get.ts +2 -0
  213. package/src/cmd/cloud/agent/list.ts +1 -0
  214. package/src/cmd/cloud/db/get.ts +1 -0
  215. package/src/cmd/cloud/db/list.ts +1 -0
  216. package/src/cmd/cloud/deploy.ts +175 -144
  217. package/src/cmd/cloud/deployment/list.ts +4 -0
  218. package/src/cmd/cloud/env/list.ts +1 -0
  219. package/src/cmd/cloud/keyvalue/get.ts +1 -0
  220. package/src/cmd/cloud/keyvalue/keys.ts +1 -0
  221. package/src/cmd/cloud/keyvalue/list-namespaces.ts +1 -0
  222. package/src/cmd/cloud/keyvalue/stats.ts +2 -0
  223. package/src/cmd/cloud/secret/list.ts +1 -0
  224. package/src/cmd/cloud/session/list.ts +4 -0
  225. package/src/cmd/cloud/storage/get.ts +1 -0
  226. package/src/cmd/cloud/storage/list.ts +4 -0
  227. package/src/cmd/cloud/stream/get.ts +1 -0
  228. package/src/cmd/cloud/stream/list.ts +1 -0
  229. package/src/cmd/cloud/vector/get.ts +1 -0
  230. package/src/cmd/cloud/vector/search.ts +1 -0
  231. package/src/cmd/dev/index.ts +323 -914
  232. package/src/cmd/dev/sync.ts +5 -1
  233. package/src/cmd/profile/show.ts +3 -4
  234. package/src/cmd/project/create.ts +3 -4
  235. package/src/cmd/project/list.ts +1 -0
  236. package/src/cmd/project/show.ts +1 -0
  237. package/src/cmd/upgrade/index.ts +6 -3
  238. package/src/config.ts +31 -11
  239. package/src/index.ts +2 -0
  240. package/src/runtime-bootstrap.ts +8 -2
  241. package/src/types.ts +48 -1
  242. package/src/utils/bun-version-checker.ts +70 -0
  243. package/src/version-check.ts +6 -2
  244. package/dist/cmd/build/bundler.d.ts +0 -22
  245. package/dist/cmd/build/bundler.d.ts.map +0 -1
  246. package/dist/cmd/build/bundler.js +0 -766
  247. package/dist/cmd/build/bundler.js.map +0 -1
  248. package/dist/cmd/build/config-loader.d.ts +0 -16
  249. package/dist/cmd/build/config-loader.d.ts.map +0 -1
  250. package/dist/cmd/build/config-loader.js +0 -227
  251. package/dist/cmd/build/config-loader.js.map +0 -1
  252. package/dist/cmd/build/file.d.ts +0 -2
  253. package/dist/cmd/build/file.d.ts.map +0 -1
  254. package/dist/cmd/build/file.js +0 -10
  255. package/dist/cmd/build/file.js.map +0 -1
  256. package/dist/cmd/build/fix-duplicate-exports.d.ts +0 -2
  257. package/dist/cmd/build/fix-duplicate-exports.d.ts.map +0 -1
  258. package/dist/cmd/build/fix-duplicate-exports.js +0 -170
  259. package/dist/cmd/build/fix-duplicate-exports.js.map +0 -1
  260. package/dist/cmd/build/plugin.d.ts +0 -6
  261. package/dist/cmd/build/plugin.d.ts.map +0 -1
  262. package/dist/cmd/build/plugin.js +0 -645
  263. package/dist/cmd/build/plugin.js.map +0 -1
  264. package/dist/cmd/build/route-discovery.d.ts +0 -54
  265. package/dist/cmd/build/route-discovery.d.ts.map +0 -1
  266. package/dist/cmd/build/route-discovery.js +0 -148
  267. package/dist/cmd/build/route-discovery.js.map +0 -1
  268. package/dist/cmd/build/route-registry.d.ts +0 -38
  269. package/dist/cmd/build/route-registry.d.ts.map +0 -1
  270. package/dist/cmd/build/route-registry.js.map +0 -1
  271. package/src/cmd/build/bundler.ts +0 -927
  272. package/src/cmd/build/config-loader.ts +0 -268
  273. package/src/cmd/build/file.ts +0 -10
  274. package/src/cmd/build/fix-duplicate-exports.ts +0 -207
  275. package/src/cmd/build/plugin.ts +0 -782
  276. package/src/cmd/build/route-discovery.ts +0 -202
  277. package/src/cmd/build/route-registry.ts +0 -222
@@ -1,24 +1,35 @@
1
- /** biome-ignore-all lint/style/useTemplate: its easier */
2
1
  import { z } from 'zod';
3
2
  import { resolve, join } from 'node:path';
4
- import { bundle } from '../build/bundler';
5
- import { getServiceUrls } from '@agentuity/server';
6
- import { getBuildMetadata } from '../build/plugin';
7
- import { existsSync, watch, statSync, readdirSync } from 'node:fs';
8
- import { getDefaultConfigDir, loadProjectSDKKey, saveProjectDir, saveConfig, loadBuildMetadata, } from '../../config';
3
+ import { existsSync } from 'node:fs';
4
+ import { internalExit } from '@agentuity/runtime';
9
5
  import { createCommand } from '../../types';
6
+ import { startBunDevServer } from '../build/vite/bun-dev-server';
10
7
  import * as tui from '../../tui';
11
- import { createAgentTemplates, createAPITemplates } from './templates';
8
+ import { getCommand } from '../../command-prefix';
12
9
  import { generateEndpoint } from './api';
13
- import { APIClient, getAppBaseURL, getAPIBaseURL, getGravityDevModeURL } from '../../api';
10
+ import { APIClient, getAPIBaseURL, getAppBaseURL, getGravityDevModeURL } from '../../api';
14
11
  import { download } from './download';
15
12
  import { createDevmodeSyncService } from './sync';
16
13
  import { getDevmodeDeploymentId } from '../build/ast';
17
- import { getWorkbench } from '../build/workbench';
18
- import { getCommand } from '../../command-prefix';
19
- import { notifyWorkbenchClients } from '../../utils/workbench-notify';
20
- import { getEnvFilePaths, readEnvFile } from '../../env-util';
21
- import { writeAgentsDocs } from '../../agents-docs';
14
+ import { getDefaultConfigDir, saveConfig } from '../../config';
15
+ const DEFAULT_PORT = 3500;
16
+ const MIN_PORT = 1024;
17
+ const MAX_PORT = 65535;
18
+ const getDefaultPort = () => {
19
+ const envPort = process.env.PORT;
20
+ if (!envPort) {
21
+ return DEFAULT_PORT;
22
+ }
23
+ const trimmed = envPort.trim();
24
+ if (!trimmed || !/^\d+$/.test(trimmed)) {
25
+ return DEFAULT_PORT;
26
+ }
27
+ const parsed = Number(trimmed);
28
+ if (!Number.isInteger(parsed) || parsed < MIN_PORT || parsed > MAX_PORT) {
29
+ return DEFAULT_PORT;
30
+ }
31
+ return parsed;
32
+ };
22
33
  const shouldDisableInteractive = (interactive) => {
23
34
  if (!interactive) {
24
35
  return true;
@@ -47,27 +58,20 @@ export const command = createCommand({
47
58
  .describe('Turn on or off the public url'),
48
59
  port: z
49
60
  .number()
50
- .min(1024) // should we allow a lower root port? probably not?
51
- .max(65535)
52
- .default(3500)
53
- .describe('The TCP port to start the dev start'),
54
- watch: z
55
- .array(z.string())
56
- .optional()
57
- .describe('Additional paths to watch for changes (e.g., --watch ../packages/workbench/dist)'),
61
+ .min(MIN_PORT)
62
+ .max(MAX_PORT)
63
+ .default(getDefaultPort())
64
+ .describe('The TCP port to start the dev server (also reads from PORT env)'),
58
65
  }),
59
66
  },
60
67
  optional: { auth: 'Continue without an account (local only)', project: true },
61
68
  async handler(ctx) {
62
- const { opts, logger, options, project, projectDir, auth } = ctx;
69
+ const { opts, logger, project, projectDir, auth } = ctx;
63
70
  let { config } = ctx;
64
- // Allow sync with mock service even without devmode endpoint
65
- const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
66
- const apiClient = new APIClient(getAPIBaseURL(config), logger, config);
67
- const syncService = createDevmodeSyncService({ logger, apiClient, mock: useMockService });
68
- const rootDir = projectDir;
71
+ const rootDir = resolve(projectDir);
69
72
  const appTs = join(rootDir, 'app.ts');
70
73
  const srcDir = join(rootDir, 'src');
74
+ // Verify required files exist
71
75
  const mustHaves = [join(rootDir, 'package.json'), appTs, srcDir];
72
76
  const missing = [];
73
77
  const interactive = !shouldDisableInteractive(opts.interactive);
@@ -81,17 +85,22 @@ export const command = createCommand({
81
85
  for (const filename of missing) {
82
86
  tui.bullet(`Missing ${filename}`);
83
87
  }
84
- process.exit(1);
88
+ internalExit(1);
85
89
  }
86
- await saveProjectDir(rootDir);
87
- // Regenerate AGENTS.md files if they are missing (e.g., after node_modules reinstall)
88
- await writeAgentsDocs(rootDir, { onlyIfMissing: true });
90
+ // Setup devmode and gravity (if using public URL)
91
+ const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
92
+ const apiClient = auth ? new APIClient(getAPIBaseURL(config), logger, config) : null;
93
+ createDevmodeSyncService({
94
+ logger,
95
+ apiClient,
96
+ mock: useMockService,
97
+ });
89
98
  let devmode;
90
99
  let gravityBin;
91
100
  let gravityURL;
92
101
  let appURL;
93
102
  if (auth && project && opts.public) {
94
- // Generate devmode endpoint only when using --public
103
+ // Generate devmode endpoint for public URL
95
104
  const endpoint = await tui.spinner({
96
105
  message: 'Connecting to Gravity',
97
106
  callback: () => {
@@ -108,11 +117,7 @@ export const command = createCommand({
108
117
  devmode = endpoint;
109
118
  gravityURL = getGravityDevModeURL(project.region, config);
110
119
  appURL = `${getAppBaseURL(config)}/r/${project.projectId}`;
111
- logger.trace('gravity url: %s', gravityURL);
112
- }
113
- logger.debug('Getting devmode deployment id for projectId: %s, endpointId: %s', project?.projectId, devmode?.id);
114
- const deploymentId = getDevmodeDeploymentId(project?.projectId ?? '', devmode?.id ?? '');
115
- if (devmode && opts.public) {
120
+ // Download gravity client
116
121
  const configDir = getDefaultConfigDir();
117
122
  const gravityDir = join(configDir, 'gravity');
118
123
  let mustCheck = true;
@@ -136,9 +141,22 @@ export const command = createCommand({
136
141
  config = _config;
137
142
  }
138
143
  }
139
- const workbench = await getWorkbench(rootDir);
140
- const canDoInput = interactive && !!(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
144
+ // Get workbench info from config (new Vite approach)
145
+ const { loadAgentuityConfig, getWorkbenchConfig } = await import('../build/vite/config-loader');
146
+ const agentuityConfig = await loadAgentuityConfig(rootDir, ctx.logger);
147
+ const workbenchConfigData = getWorkbenchConfig(agentuityConfig, true); // dev mode
148
+ const workbench = {
149
+ hasWorkbench: workbenchConfigData.enabled,
150
+ config: workbenchConfigData.enabled
151
+ ? { route: workbenchConfigData.route, headers: workbenchConfigData.headers }
152
+ : null,
153
+ };
154
+ const deploymentId = getDevmodeDeploymentId(project?.projectId ?? '', devmode?.id ?? '');
155
+ // Calculate URLs for banner
141
156
  const padding = 12;
157
+ const workbenchUrl = auth && project?.projectId
158
+ ? `${getAppBaseURL(config)}/w/${project.projectId}`
159
+ : `http://127.0.0.1:${opts.port}${workbench.config?.route ?? '/workbench'}`;
142
160
  const devmodebody = tui.muted(tui.padRight('Local:', padding)) +
143
161
  tui.link(`http://127.0.0.1:${opts.port}`) +
144
162
  '\n' +
@@ -146,764 +164,286 @@ export const command = createCommand({
146
164
  (devmode?.hostname ? tui.link(`https://${devmode.hostname}`) : tui.warn('Disabled')) +
147
165
  '\n' +
148
166
  tui.muted(tui.padRight('Workbench:', padding)) +
149
- (workbench.hasWorkbench
150
- ? tui.link(`http://127.0.0.1:${opts.port}${workbench.config?.route ?? '/workbench'}`)
151
- : tui.warn('Disabled')) +
167
+ (workbench.hasWorkbench ? tui.link(workbenchUrl) : tui.warn('Disabled')) +
152
168
  '\n' +
153
169
  tui.muted(tui.padRight('Dashboard:', padding)) +
154
170
  (appURL ? tui.link(appURL) : tui.warn('Disabled')) +
155
171
  '\n' +
156
- (canDoInput
172
+ (interactive
157
173
  ? '\n' + tui.muted('Press ') + tui.bold('h') + tui.muted(' for keyboard shortcuts')
158
174
  : '');
159
- function showBanner() {
160
- tui.banner('⨺ Agentuity DevMode', devmodebody, {
161
- padding: 2,
162
- topSpacer: false,
163
- bottomSpacer: false,
164
- centerTitle: false,
165
- });
166
- }
167
- showBanner();
168
- // Load .env file(s) based on config profile (Bun no longer auto-loads .env files)
169
- const isProduction = process.env.NODE_ENV === 'production' || config?.name !== 'local';
170
- const envFiles = getEnvFilePaths(rootDir, {
171
- configName: config?.name,
172
- isProduction,
175
+ tui.banner('⨺ Agentuity DevMode', devmodebody, {
176
+ padding: 2,
177
+ topSpacer: false,
178
+ bottomSpacer: false,
179
+ centerTitle: false,
173
180
  });
174
- // Load and merge all .env files (later files override earlier ones)
175
- let envVars = {};
176
- for (const envFilePath of envFiles) {
177
- if (await Bun.file(envFilePath).exists()) {
178
- const vars = await readEnvFile(envFilePath);
179
- envVars = { ...envVars, ...vars };
180
- logger.debug('Loaded environment variables from %s', envFilePath);
181
- }
182
- }
183
- // Start with process.env and merge in .env file vars
184
- const env = { ...process.env, ...envVars };
185
- env.AGENTUITY_SDK_DEV_MODE = 'true';
186
- env.AGENTUITY_ENV = 'development';
187
- env.NODE_ENV = 'development';
188
- env.AGENTUITY_REGION = project?.region;
189
- env.PORT = Number(opts.port).toFixed();
190
- env.AGENTUITY_PORT = env.PORT;
191
- const serviceUrls = getServiceUrls(project?.region);
192
- if (options.logLevel !== undefined)
193
- env.AGENTUITY_LOG_LEVEL = options.logLevel;
194
- // Pass through AGENTUITY_SDK_LOG_LEVEL for internal SDK logger
195
- if (process.env.AGENTUITY_SDK_LOG_LEVEL) {
196
- env.AGENTUITY_SDK_LOG_LEVEL = process.env.AGENTUITY_SDK_LOG_LEVEL;
197
- }
198
- env.AGENTUITY_FORCE_LOCAL_SERVICES = opts.local === true ? 'true' : 'false';
199
- if (project) {
200
- env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
201
- env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
202
- env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
203
- env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
204
- env.AGENTUITY_STREAM_URL = serviceUrls.stream;
205
- env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
206
- env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
207
- }
208
- if (!process.stdout.isTTY) {
209
- env.NO_COLOR = '1';
210
- }
211
- const agentuityDir = resolve(rootDir, '.agentuity');
212
- const appPath = resolve(agentuityDir, 'app.js');
213
- // Load existing metadata file to use as previousMetadata for sync
214
- // This prevents reinserting agents/evals that haven't changed
215
- let previousMetadata;
216
- try {
217
- previousMetadata = await loadBuildMetadata(agentuityDir);
218
- logger.debug('Loaded previous metadata with %d agent(s)', previousMetadata.agents?.length ?? 0);
219
- }
220
- catch (_error) {
221
- // File doesn't exist yet (first run), that's okay
222
- logger.debug('No previous metadata file found, will treat all agents/evals as new');
223
- previousMetadata = undefined;
224
- }
225
- // Watch directories instead of files to survive atomic replacements (sed -i, cp)
226
- const watches = [rootDir];
227
- // Add additional watch paths from options
228
- if (opts.watch) {
229
- for (const watchPath of opts.watch) {
230
- const resolvedPath = resolve(rootDir, watchPath);
231
- if (existsSync(resolvedPath)) {
232
- watches.push(resolvedPath);
233
- logger.debug('Added additional watch path: %s', resolvedPath);
234
- }
235
- else {
236
- logger.warn('Watch path does not exist: %s', resolvedPath);
237
- }
238
- }
239
- }
240
- const watchers = [];
241
- let failures = 0;
242
- let running = false;
243
- let pid = 0;
244
- let failed = false;
245
- let devServer;
246
- let exitPromise;
247
- let restarting = false;
248
- let shuttingDownForRestart = false;
249
- let pendingRestart = false;
250
- let building = false;
251
- let buildCompletedAt = 0;
252
- const BUILD_COOLDOWN_MS = 500; // Ignore file changes for 500ms after build completes
253
- const templatedDirectories = new Map(); // Track directories that just had templates created
254
- let metadata;
255
- let showInitialReadyMessage = true;
256
- let serverStartTime = 0;
257
- let gravityClient;
258
- let initialStartupComplete = false;
259
- const sdkKey = await loadProjectSDKKey(logger, rootDir);
260
- if (!sdkKey) {
261
- tui.warning(`Couldn't find the AGENTUITY_SDK_KEY in ${rootDir} .env file`);
262
- }
263
- const gravityBinExists = gravityBin ? await Bun.file(gravityBin).exists() : true;
264
- if (!gravityBinExists) {
265
- logger.error(`Gravity binary not found at ${gravityBin}, skipping gravity client startup`);
266
- }
267
- async function restartGravityClient() {
268
- if (gravityClient) {
269
- gravityClient.kill('SIGINT');
270
- gravityClient.kill();
271
- }
272
- if (!devmode || !opts.public) {
273
- return;
274
- }
275
- try {
276
- gravityClient = Bun.spawn([
277
- gravityBin,
278
- '--endpoint-id',
279
- devmode.id,
280
- '--port',
281
- env.PORT,
282
- '--url',
283
- gravityURL,
284
- '--log-level',
285
- process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
286
- ], {
287
- cwd: rootDir,
288
- stdout: 'inherit',
289
- stderr: 'inherit',
290
- stdin: 'ignore',
291
- env: { ...env, AGENTUITY_SDK_KEY: sdkKey },
292
- });
293
- gravityClient.exited.then(() => {
294
- logger.debug('gravity client exited');
295
- });
296
- }
297
- catch (err) {
298
- logger.error('Failed to spawn gravity client: %s', err instanceof Error ? err.message : String(err));
299
- }
300
- }
301
- // Track restart timestamps to detect restart loops
302
- const restartTimestamps = [];
303
- const MAX_RESTARTS = 10;
304
- const TIME_WINDOW_MS = 10000; // 10 seconds
305
- function checkRestartThrottle() {
306
- const now = Date.now();
307
- restartTimestamps.push(now);
308
- // Remove timestamps older than the time window
309
- while (restartTimestamps.length > 0 && now - restartTimestamps[0] > TIME_WINDOW_MS) {
310
- restartTimestamps.shift();
311
- }
312
- // Check if we've exceeded the threshold
313
- if (restartTimestamps.length >= MAX_RESTARTS) {
314
- tui.error(`Detected ${MAX_RESTARTS} restarts in ${TIME_WINDOW_MS / 1000} seconds`);
315
- tui.error('This usually indicates a file watcher loop (e.g., log files in the project root)');
316
- tui.fatal('Too many rapid restarts, exiting to prevent infinite loop');
317
- }
318
- }
319
- let lastErrorLineCount = 0;
320
- let showedRestartMessage = false;
321
- function clearRestartMessage() {
322
- if (showedRestartMessage) {
323
- process.stdout.write('\x1b[1A\x1b[2K');
324
- showedRestartMessage = false;
325
- }
326
- }
327
- function failure(msg) {
328
- failed = true;
329
- failures++;
330
- // Exit immediately on initial startup failure
331
- if (!initialStartupComplete) {
332
- tui.fatal(msg);
333
- }
334
- // During hot reload, show error but don't exit unless too many failures
335
- if (failures >= 5) {
336
- tui.error(msg);
337
- tui.fatal('too many failures, exiting');
338
- }
339
- else {
340
- // Ensure we're on a new line before printing error
341
- tui.error(msg);
342
- // Track lines: 1 for "✗ Building..." + 1 for error message
343
- lastErrorLineCount = 2;
344
- }
345
- }
346
- function clearLastError() {
347
- if (lastErrorLineCount > 0) {
348
- // Move cursor up and clear each line
349
- for (let i = 0; i < lastErrorLineCount; i++) {
350
- process.stdout.write('\x1b[1A\x1b[2K');
351
- }
352
- lastErrorLineCount = 0;
353
- }
354
- }
355
- const kill = async () => {
356
- if (!running || !devServer) {
357
- logger.trace('kill() called but server not running');
358
- return;
359
- }
360
- logger.trace('Killing dev server (pid: %d)', pid);
361
- shuttingDownForRestart = true;
362
- running = false;
363
- process.kill(pid, 'SIGINT');
364
- try {
365
- // Kill the process group (negative PID kills entire group)
366
- process.kill(-pid, 'SIGTERM');
367
- logger.trace('Sent SIGTERM to process group');
368
- }
369
- catch {
370
- // Fallback: kill the direct process
181
+ // Restart loop - allows server to restart on file changes
182
+ let shouldRestart = false;
183
+ let viteServer = null;
184
+ let gravityProcess = null;
185
+ const restartServer = () => {
186
+ shouldRestart = true;
187
+ };
188
+ const showWelcome = () => {
189
+ logger.info('DevMode ready 🚀');
190
+ };
191
+ // Make restart function available globally for HMR plugin
192
+ globalThis.__AGENTUITY_RESTART__ = restartServer;
193
+ // Setup signal handlers once before the loop
194
+ const cleanup = async () => {
195
+ tui.info('Shutting down...');
196
+ // Close Vite asset server first
197
+ if (viteServer) {
198
+ await viteServer.close();
199
+ }
200
+ // Kill gravity client with SIGTERM first, then SIGKILL as fallback
201
+ if (gravityProcess) {
371
202
  try {
372
- if (devServer) {
373
- devServer.kill();
374
- logger.trace('Killed dev server process directly');
203
+ gravityProcess.kill('SIGTERM');
204
+ // Give it a moment to gracefully shutdown
205
+ await new Promise((resolve) => setTimeout(resolve, 100));
206
+ if (gravityProcess.exitCode === null) {
207
+ gravityProcess.kill('SIGKILL');
375
208
  }
376
209
  }
377
- catch {
378
- // Ignore if already dead
379
- logger.trace('Process already dead');
210
+ catch (err) {
211
+ logger.debug('Error killing gravity process: %s', err);
380
212
  }
381
213
  }
382
- // Wait for the server to actually exit
383
- if (exitPromise) {
384
- logger.trace('Waiting for dev server to exit...');
385
- await exitPromise;
386
- logger.trace('Dev server exited');
387
- }
388
- devServer = undefined;
389
- exitPromise = undefined;
390
- shuttingDownForRestart = false;
391
- };
392
- // Handle signals to ensure entire process tree is killed
393
- const cleanup = (exitCode = 0) => {
394
- logger.trace('cleanup() called with exitCode=%d', exitCode);
395
- if (gravityClient) {
396
- logger.debug('calling kill on gravity client with pid: %d', gravityClient.pid);
397
- gravityClient.kill('SIGINT');
398
- gravityClient = undefined;
399
- }
400
- if (pid && running) {
401
- kill();
402
- }
403
- for (const watcher of watchers) {
404
- watcher.close();
405
- }
406
- watchers.length = 0;
407
- process.exit(exitCode);
214
+ internalExit(0);
408
215
  };
409
216
  process.on('SIGINT', cleanup);
410
217
  process.on('SIGTERM', cleanup);
411
- process.on('SIGQUIT', cleanup);
218
+ // Ensure gravity client is always killed on exit (even if cleanup is bypassed)
219
+ // Use SIGKILL for immediate termination since the process is already exiting
412
220
  process.on('exit', () => {
413
- // Synchronous cleanup on exit
414
- if (gravityClient) {
221
+ if (gravityProcess && gravityProcess.exitCode === null) {
415
222
  try {
416
- gravityClient.kill('SIGINT');
223
+ gravityProcess.kill('SIGKILL');
417
224
  }
418
- catch {
419
- // Ignore errors
225
+ catch (_err) {
226
+ // Ignore errors during exit cleanup
420
227
  }
421
228
  }
422
229
  });
423
- async function restart() {
424
- // Queue restart if already restarting
425
- if (restarting) {
426
- logger.trace('Restart already in progress, queuing another restart');
427
- pendingRestart = true;
428
- return;
230
+ while (true) {
231
+ shouldRestart = false;
232
+ try {
233
+ // Generate entry file for Vite before starting dev server
234
+ await tui.spinner({
235
+ message: 'Generating entry file',
236
+ callback: async () => {
237
+ const { generateEntryFile } = await import('../build/entry-generator');
238
+ await generateEntryFile({
239
+ rootDir,
240
+ projectId: project?.projectId ?? '',
241
+ deploymentId,
242
+ logger,
243
+ mode: 'dev',
244
+ });
245
+ },
246
+ clearOnSuccess: true,
247
+ });
248
+ }
249
+ catch (error) {
250
+ tui.error(`Failed to generate entry file: ${error}`);
251
+ tui.warn('Waiting for file changes to retry...');
252
+ // Wait for next restart trigger
253
+ await new Promise((resolve) => {
254
+ const checkRestart = setInterval(() => {
255
+ if (shouldRestart) {
256
+ clearInterval(checkRestart);
257
+ resolve();
258
+ }
259
+ }, 100);
260
+ });
261
+ continue;
429
262
  }
430
- logger.trace('restart() called, restarting=%s, running=%s', restarting, running);
431
- restarting = true;
432
- pendingRestart = false;
433
- failed = false;
434
263
  try {
435
- if (running) {
436
- logger.trace('Server is running, killing before restart');
437
- checkRestartThrottle();
438
- tui.info('Restarting on file change');
439
- showedRestartMessage = true;
440
- // Notify workbench clients before killing the server
441
- await notifyWorkbenchClients({
442
- port: opts.port,
443
- message: 'restarting',
444
- });
445
- // Small delay to ensure the restart message is processed before killing server
446
- await new Promise((resolve) => setTimeout(resolve, 200));
447
- await kill();
448
- logger.trace('Server killed, continuing with restart');
449
- // Continue with restart after kill completes
450
- }
451
- else {
452
- logger.trace('Initial server start');
453
- }
454
- logger.trace('Starting typecheck and build...');
455
- // Clear any previous error before starting new build
456
- clearLastError();
457
- clearRestartMessage();
458
- try {
459
- await tui.spinner({
460
- message: 'Building...',
461
- clearOnSuccess: true,
462
- callback: async () => {
463
- logger.trace('Bundle starting...');
464
- building = true;
465
- await bundle({
466
- rootDir,
467
- dev: true,
468
- projectId: project?.projectId,
469
- deploymentId,
470
- port: opts.port,
471
- region: project?.region ?? 'local',
472
- logger,
473
- workbench,
474
- });
475
- building = false;
476
- buildCompletedAt = Date.now();
477
- logger.trace('Bundle completed successfully');
478
- logger.trace('tsc starting...');
479
- const tscExitCode = await tui.runCommand({
480
- command: 'tsc',
481
- cmd: ['bunx', 'tsc', '--noEmit'],
482
- cwd: rootDir,
483
- clearOnSuccess: true,
484
- truncate: false,
485
- maxLinesOutput: 2,
486
- maxLinesOnFailure: 15,
487
- });
488
- if (tscExitCode !== 0) {
489
- logger.trace('tsc failed with exit code %d', tscExitCode);
490
- failure('Type check failed');
491
- return;
492
- }
493
- logger.trace('tsc completed successfully');
494
- await restartGravityClient();
495
- },
264
+ // Start Bun dev server (with Vite asset server for HMR)
265
+ const result = await startBunDevServer({
266
+ rootDir,
267
+ port: opts.port,
268
+ projectId: project?.projectId,
269
+ orgId: project?.orgId,
270
+ deploymentId,
271
+ logger,
272
+ });
273
+ viteServer = result.viteAssetServer.server;
274
+ // Note: Bun server runs in-process, no separate app process needed
275
+ // Wait for app.ts to finish loading (Vite is ready but app may still be initializing)
276
+ // Give it 2 seconds to ensure app initialization completes
277
+ await new Promise((resolve) => setTimeout(resolve, 2000));
278
+ }
279
+ catch (error) {
280
+ tui.error(`Failed to start dev server: ${error}`);
281
+ tui.warn('Waiting for file changes to retry...');
282
+ // Wait for next restart trigger
283
+ await new Promise((resolve) => {
284
+ const checkRestart = setInterval(() => {
285
+ if (shouldRestart) {
286
+ clearInterval(checkRestart);
287
+ resolve();
288
+ }
289
+ }, 100);
290
+ });
291
+ continue;
292
+ }
293
+ try {
294
+ // Start gravity client if we have devmode
295
+ if (gravityBin && gravityURL && devmode) {
296
+ logger.trace('Starting gravity client: %s', gravityBin);
297
+ gravityProcess = Bun.spawn([
298
+ gravityBin,
299
+ '--endpoint-id',
300
+ devmode.id,
301
+ '--port',
302
+ opts.port.toString(),
303
+ '--url',
304
+ gravityURL,
305
+ '--log-level',
306
+ process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
307
+ ], {
308
+ cwd: rootDir,
309
+ stdout: 'pipe',
310
+ stderr: 'pipe',
311
+ detached: false, // Ensure gravity dies with parent process
496
312
  });
497
- }
498
- catch (error) {
499
- building = false;
500
- const e = error;
501
- if (e.constructor.name === 'AggregateError') {
502
- const ex = e;
503
- for (const err of ex.errors) {
504
- if (err) {
505
- failure(err);
506
- return;
313
+ // Log gravity output
314
+ (async () => {
315
+ try {
316
+ if (gravityProcess?.stdout) {
317
+ for await (const chunk of gravityProcess.stdout) {
318
+ const text = new TextDecoder().decode(chunk);
319
+ logger.debug('[gravity] %s', text.trim());
320
+ }
507
321
  }
508
322
  }
509
- return;
510
- }
511
- const errorMsg = error instanceof Error ? error.message : String(error);
512
- failure(errorMsg);
513
- return;
514
- }
515
- logger.trace('Typecheck and build completed');
516
- if (failed) {
517
- logger.trace('Restart failed, returning early');
518
- return;
519
- }
520
- // Reset failure counter on successful build
521
- failures = 0;
522
- logger.trace('Checking if app file exists: %s', appPath);
523
- if (!existsSync(appPath)) {
524
- logger.trace('App file not found: %s', appPath);
525
- failure(`App file not found: ${appPath}`);
526
- return;
527
- }
528
- logger.trace('App file exists, getting build metadata...');
529
- metadata = getBuildMetadata();
530
- logger.trace('Build metadata retrieved');
531
- // Sync agents and evals to API if in devmode with auth
532
- if (auth && project && apiClient) {
533
- try {
534
- logger.debug('Loading build metadata for sync...');
535
- const currentMetadata = await loadBuildMetadata(agentuityDir);
536
- logger.debug('Found %d agent(s) and %d route(s) in metadata', currentMetadata.agents?.length ?? 0, currentMetadata.routes?.length ?? 0);
537
- if (currentMetadata.agents) {
538
- for (const agent of currentMetadata.agents) {
539
- logger.debug('Agent: id=%s, name=%s, version=%s, evals=%d', agent.id, agent.name, agent.version, agent.evals?.length ?? 0);
540
- if (agent.evals) {
541
- for (const evalItem of agent.evals) {
542
- logger.debug(' Eval: id=%s, name=%s, version=%s', evalItem.id, evalItem.name, evalItem.version);
543
- }
323
+ catch (err) {
324
+ logger.error('Error reading gravity stdout: %s', err);
325
+ }
326
+ })();
327
+ (async () => {
328
+ try {
329
+ if (gravityProcess?.stderr) {
330
+ for await (const chunk of gravityProcess.stderr) {
331
+ const text = new TextDecoder().decode(chunk);
332
+ logger.warn('[gravity] %s', text.trim());
544
333
  }
545
334
  }
546
335
  }
547
- logger.debug('Syncing agents and evals...');
548
- await syncService.sync(currentMetadata, previousMetadata, project.projectId, deploymentId);
549
- previousMetadata = currentMetadata;
550
- logger.debug('Sync completed successfully');
551
- }
552
- catch (error) {
553
- logger.error('Failed to sync agents/evals: %s', error);
554
- if (error instanceof Error) {
555
- logger.error('Error stack: %s', error.stack);
336
+ catch (err) {
337
+ logger.error('Error reading gravity stderr: %s', err);
556
338
  }
557
- // Don't fail the build, just log the error
558
- }
339
+ })();
340
+ logger.debug('Gravity client started');
559
341
  }
560
- else {
561
- logger.trace('Skipping sync - auth=%s, project=%s, devmode=%s, apiClient=%s', !!auth, !!project, !!devmode, !!apiClient);
342
+ // Sync service integration
343
+ // TODO: Integrate sync service with Vite's buildStart/buildEnd hooks
344
+ // The sync service will be called when metadata changes are detected
345
+ // Handle keyboard shortcuts
346
+ if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
347
+ process.stdin.setRawMode(true);
348
+ process.stdin.resume();
349
+ process.stdin.setEncoding('utf8');
350
+ const showHelp = () => {
351
+ console.log('\n' + tui.bold('Keyboard Shortcuts:'));
352
+ console.log(tui.muted(' h') + ' - show this help');
353
+ console.log(tui.muted(' c') + ' - clear console');
354
+ console.log(tui.muted(' q') + ' - quit\n');
355
+ };
356
+ process.stdin.on('data', (data) => {
357
+ const key = data.toString();
358
+ // Handle Ctrl+C
359
+ if (key === '\u0003') {
360
+ internalExit(0);
361
+ }
362
+ switch (key) {
363
+ case 'h':
364
+ showHelp();
365
+ break;
366
+ case 'c':
367
+ console.clear();
368
+ tui.banner('⨺ Agentuity DevMode', devmodebody, {
369
+ padding: 2,
370
+ topSpacer: false,
371
+ bottomSpacer: false,
372
+ centerTitle: false,
373
+ });
374
+ break;
375
+ case 'q':
376
+ internalExit(0);
377
+ break;
378
+ default:
379
+ console.log(data);
380
+ break;
381
+ }
382
+ });
562
383
  }
563
- logger.trace('Starting dev server: %s', appPath);
564
- // Use shell to run in a process group for proper cleanup
565
- // The 'exec' ensures the shell is replaced by the actual process
566
- logger.trace('Spawning dev server process...');
567
- devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], {
568
- cwd: rootDir,
569
- stdout: 'inherit',
570
- stderr: 'inherit',
571
- stdin: process.stdin.isTTY ? 'ignore' : 'inherit', // Don't inherit stdin, we handle it ourselves
572
- env,
384
+ showWelcome();
385
+ // Wait for restart signal
386
+ await new Promise((resolve) => {
387
+ const checkRestart = setInterval(() => {
388
+ if (shouldRestart) {
389
+ clearInterval(checkRestart);
390
+ resolve();
391
+ }
392
+ }, 100);
573
393
  });
574
- logger.trace('Dev server process spawned, setting up state...');
575
- running = true;
576
- failed = false;
577
- pid = devServer.pid;
578
- exitPromise = devServer.exited;
579
- serverStartTime = Date.now();
580
- logger.trace('Dev server started (pid: %d)', pid);
581
- if (showInitialReadyMessage) {
582
- showInitialReadyMessage = false;
583
- logger.info('DevMode ready 🚀');
584
- logger.trace('Initial ready message logged');
585
- // Mark initial startup complete immediately to prevent watcher restarts
586
- initialStartupComplete = true;
587
- logger.trace('Initial startup complete, file watcher restarts now enabled');
394
+ // Restart triggered - cleanup and loop
395
+ tui.info('Restarting server...');
396
+ // Close Vite asset server
397
+ if (viteServer) {
398
+ await viteServer.close();
588
399
  }
589
- // Notify workbench clients that the server is alive and ready
590
- // Use setTimeout to ensure server is fully ready before notifying
591
- setTimeout(async () => {
592
- await notifyWorkbenchClients({
593
- port: opts.port,
594
- message: 'alive',
595
- });
596
- }, 500);
597
- logger.trace('Attaching exit handler to dev server process...');
598
- // Attach non-blocking exit handler
599
- exitPromise
600
- .then((exitCode) => {
601
- const runtime = Date.now() - serverStartTime;
602
- logger.trace('Dev server exited with code %d (shuttingDownForRestart=%s, runtime=%dms)', exitCode, shuttingDownForRestart, runtime);
603
- running = false;
604
- devServer = undefined;
605
- exitPromise = undefined;
606
- // If server exited immediately after starting (< 2 seconds), treat as failure and restart
607
- if (runtime < 2000 && !shuttingDownForRestart) {
608
- logger.trace('Server exited too quickly, treating as failure and restarting');
609
- failure('Server exited immediately after starting');
610
- // Trigger a restart after a short delay
611
- setTimeout(() => {
612
- if (!running && !restarting) {
613
- logger.trace('Triggering restart after quick exit');
614
- restart();
615
- }
616
- }, 100);
617
- return;
618
- }
619
- // Only exit the CLI if this is a clean exit AND not a restart AND server ran for a while
620
- if (exitCode === 0 && !shuttingDownForRestart && runtime >= 2000) {
621
- logger.trace('Clean exit, stopping CLI');
622
- cleanup(exitCode);
623
- }
624
- // Non-zero exit codes are treated as restartable failures
625
- // But if it's exit code 1 (common error exit), also exit the CLI
626
- if (exitCode === 1 && !shuttingDownForRestart && runtime >= 2000) {
627
- logger.trace('Server exited with error code 1, stopping CLI');
628
- cleanup(exitCode);
629
- }
630
- })
631
- .catch((error) => {
632
- logger.trace('Dev server exit error (shuttingDownForRestart=%s): %s', shuttingDownForRestart, error);
633
- running = false;
634
- devServer = undefined;
635
- exitPromise = undefined;
636
- if (!shuttingDownForRestart) {
637
- if (error instanceof Error) {
638
- failure(`Dev server failed: ${error.message}`);
639
- }
640
- else {
641
- failure('Dev server failed');
400
+ if (gravityProcess) {
401
+ try {
402
+ gravityProcess.kill('SIGTERM');
403
+ await new Promise((resolve) => setTimeout(resolve, 100));
404
+ if (gravityProcess.exitCode === null) {
405
+ gravityProcess.kill('SIGKILL');
642
406
  }
643
407
  }
644
- });
645
- }
646
- catch (error) {
647
- logger.trace('Restart caught error: %s', error);
648
- if (error instanceof Error) {
649
- logger.trace('Error message: %s, stack: %s', error.message, error.stack);
650
- failure(`Dev server failed: ${error.message}`);
651
- }
652
- else {
653
- logger.trace('Non-Error exception: %s', String(error));
654
- failure('Dev server failed');
655
- }
656
- running = false;
657
- devServer = undefined;
658
- }
659
- finally {
660
- logger.trace('Entering restart() finally block...');
661
- const hadPendingRestart = pendingRestart;
662
- restarting = false;
663
- pendingRestart = false;
664
- logger.trace('restart() completed, restarting=%s, hadPendingRestart=%s', restarting, hadPendingRestart);
665
- // If another restart was queued while we were restarting, trigger it now
666
- if (hadPendingRestart) {
667
- logger.trace('Triggering queued restart');
668
- setImmediate(restart);
408
+ catch (err) {
409
+ logger.debug('Error killing gravity process during restart: %s', err);
410
+ }
669
411
  }
412
+ // Brief pause before restart
413
+ await new Promise((resolve) => setTimeout(resolve, 500));
670
414
  }
671
- }
672
- logger.trace('Starting initial build and server');
673
- await restart();
674
- logger.trace('Initial restart completed, setting up watchers');
675
- logger.trace('initialStartupComplete is now: %s', initialStartupComplete);
676
- // Setup keyboard shortcuts (only if we have a TTY)
677
- if (canDoInput) {
678
- logger.trace('Setting up keyboard shortcuts');
679
- process.stdin.setRawMode(true);
680
- process.stdin.resume();
681
- process.stdin.setEncoding('utf8');
682
- const showHelp = () => {
683
- console.log('\n' + tui.bold('Keyboard Shortcuts:'));
684
- console.log(tui.muted(' h') + ' - show this help');
685
- console.log(tui.muted(' c') + ' - clear console');
686
- console.log(tui.muted(' r') + ' - restart server');
687
- console.log(tui.muted(' o') + ' - show routes');
688
- console.log(tui.muted(' a') + ' - show agents');
689
- console.log(tui.muted(' q') + ' - quit\n');
690
- };
691
- const showRoutes = () => {
692
- tui.info('API Route Detail');
693
- tui.table(metadata?.routes ?? [], ['method', 'path', 'filename']);
694
- };
695
- const showAgents = () => {
696
- tui.info('Agent Detail');
697
- tui.table(metadata?.agents ?? [], ['name', 'filename', 'description']);
698
- };
699
- process.stdin.on('data', (data) => {
700
- const key = data.toString();
701
- // Handle Ctrl+C
702
- if (key === '\u0003') {
703
- cleanup();
704
- return;
705
- }
706
- // Handle other shortcuts
707
- switch (key) {
708
- case 'h':
709
- showHelp();
710
- break;
711
- case 'c':
712
- console.clear();
713
- showBanner();
714
- break;
715
- case 'r':
716
- tui.info('Manually restarting server...');
717
- restart();
718
- break;
719
- case 'o':
720
- showRoutes();
721
- break;
722
- case 'a':
723
- showAgents();
724
- break;
725
- case 'q':
726
- tui.info('Shutting down...');
727
- cleanup();
728
- break;
729
- }
730
- });
731
- logger.trace('✓ Keyboard shortcuts enabled');
732
- }
733
- else {
734
- if (process.stdin) {
735
- // still need to monitor stdin in case we are pipeing into another process or file etc
736
- if (typeof process.stdin.setRawMode === 'function') {
737
- process.stdin.setRawMode(true);
415
+ catch (error) {
416
+ tui.error(`Error during server operation: ${error}`);
417
+ tui.warn('Waiting for file changes to retry...');
418
+ // Cleanup on error - close Vite asset server
419
+ if (viteServer) {
420
+ await viteServer.close();
738
421
  }
739
- process.stdin.resume();
740
- process.stdin.on('data', (data) => {
741
- const key = data.toString();
742
- // Handle Ctrl+C
743
- if (key === '\u0003') {
744
- cleanup();
745
- return;
746
- }
747
- });
748
- }
749
- logger.trace('❌ Keyboard shortcuts disabled');
750
- }
751
- // Patterns to ignore (generated files that change during build)
752
- const ignorePatterns = [
753
- /\.generated\.(js|ts|d\.ts)$/,
754
- /registry\.generated\.ts$/,
755
- /types\.generated\.d\.ts$/,
756
- /client\.generated\.js$/,
757
- /\.tmp$/,
758
- /\.tsbuildinfo$/,
759
- /\.agentuity\//,
760
- // Ignore temporary files created by sed (e.g., sedUprJj0)
761
- /\/sed[A-Za-z0-9]+$/,
762
- ];
763
- // Helper to check if a file is a temporary file created by sed
764
- const isSedTempFile = (filePath) => {
765
- const basename = filePath.split('/').pop() || '';
766
- return /^sed[A-Za-z0-9]+$/.test(basename);
767
- };
768
- logger.trace('Setting up file watchers for: %s', watches.join(', '));
769
- for (const watchDir of watches) {
770
- try {
771
- logger.trace('Setting up watcher for %s', watchDir);
772
- const watcher = watch(watchDir, { recursive: true }, (eventType, changedFile) => {
773
- const absPath = changedFile ? resolve(watchDir, changedFile) : watchDir;
774
- // Ignore file changes during initial startup to prevent spurious restarts
775
- if (!initialStartupComplete) {
776
- logger.trace('File change ignored (initial startup): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
777
- return;
778
- }
779
- // Ignore file changes during active build to prevent loops
780
- if (building) {
781
- logger.trace('File change ignored (build in progress): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
782
- return;
783
- }
784
- // Ignore file changes immediately after build completes (cooldown period)
785
- // This prevents restarts from build output files that are written asynchronously
786
- if (buildCompletedAt > 0 && Date.now() - buildCompletedAt < BUILD_COOLDOWN_MS) {
787
- logger.trace('File change ignored (build cooldown): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
788
- return;
789
- }
790
- // Ignore node_modules folder
791
- if (absPath.includes('node_modules')) {
792
- logger.trace('File change ignored (node_modules): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
793
- return;
794
- }
795
- // Ignore .git folder
796
- if (changedFile && (changedFile === '.git' || changedFile.startsWith('.git/'))) {
797
- logger.trace('File change ignored (.git folder): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
798
- return;
799
- }
800
- // Ignore changes in .agentuity directory (build output)
801
- // Check both relative path and normalized absolute path
802
- const isInAgentuityDir = (changedFile &&
803
- (changedFile === '.agentuity' || changedFile.startsWith('.agentuity/'))) ||
804
- resolve(absPath).startsWith(agentuityDir);
805
- if (isInAgentuityDir) {
806
- logger.trace('File change ignored (.agentuity dir): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
807
- return;
808
- }
809
- // Ignore changes to src/web/public directory (static assets, not code)
810
- if (changedFile && changedFile === 'src/web/public') {
811
- logger.trace('File change ignored (static assets dir): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
812
- return;
813
- }
814
- // Check for .tmp file renames that replace watched files (BEFORE ignoring)
815
- // This handles cases like sed -i.tmp where agent.ts.tmp is renamed to agent.ts
816
- if (eventType === 'rename' && changedFile && changedFile.endsWith('.tmp')) {
817
- const targetFile = changedFile.slice(0, -4); // Remove .tmp suffix
818
- const targetAbsPath = resolve(watchDir, targetFile);
819
- // Only trigger restart for source files (ts, tsx, js, jsx, etc.)
820
- const isSourceFile = /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(targetFile);
821
- // Check if target file exists and is not in ignored directories
822
- const targetExists = existsSync(targetAbsPath);
823
- const inNodeModules = targetAbsPath.includes('node_modules');
824
- const inAgentuityDir = (targetFile &&
825
- (targetFile === '.agentuity' || targetFile.startsWith('.agentuity/'))) ||
826
- resolve(targetAbsPath).startsWith(agentuityDir);
827
- let isDirectory = false;
828
- if (targetExists) {
829
- try {
830
- isDirectory = statSync(targetAbsPath).isDirectory();
831
- }
832
- catch (err) {
833
- logger.trace('Failed to stat target file: %s', err);
834
- }
835
- }
836
- if (isSourceFile &&
837
- targetExists &&
838
- !inNodeModules &&
839
- !inAgentuityDir &&
840
- !isDirectory) {
841
- logger.trace('File change detected (temp file rename): %s -> %s', absPath, targetAbsPath);
842
- restart();
843
- return;
844
- }
845
- }
846
- // Ignore generated files to prevent restart loops
847
- if (changedFile) {
848
- // Check for sed temporary files
849
- if (isSedTempFile(changedFile)) {
850
- logger.trace('File change ignored (sed temp file): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
851
- return;
852
- }
853
- // Check other ignore patterns
854
- for (const pattern of ignorePatterns) {
855
- if (pattern.test(changedFile)) {
856
- logger.trace('File change ignored (generated file): %s (event: %s, file: %s)', watchDir, eventType, changedFile);
857
- return;
858
- }
422
+ if (gravityProcess) {
423
+ try {
424
+ gravityProcess.kill('SIGTERM');
425
+ await new Promise((resolve) => setTimeout(resolve, 100));
426
+ if (gravityProcess.exitCode === null) {
427
+ gravityProcess.kill('SIGKILL');
859
428
  }
860
429
  }
861
- // Handle new empty directories in src/agent/ or src/api/ paths
862
- if (eventType === 'rename' &&
863
- existsSync(absPath) &&
864
- statSync(absPath).isDirectory() &&
865
- readdirSync(absPath).length === 0) {
866
- if (changedFile?.startsWith('src/agent/')) {
867
- logger.debug('agent directory created: %s', changedFile);
868
- createAgentTemplates(absPath);
869
- // Mark this directory as recently templated to avoid immediate rebuild
870
- templatedDirectories.set(absPath, Date.now());
871
- // Schedule cleanup of this marker after enough time for file events
872
- setTimeout(() => templatedDirectories.delete(absPath), 1000);
873
- // Don't restart - wait for the template files to trigger the rebuild
874
- return;
875
- }
876
- else if (changedFile?.startsWith('src/api/')) {
877
- logger.debug('api directory created: %s', changedFile);
878
- createAPITemplates(absPath);
879
- // Mark this directory as recently templated to avoid immediate rebuild
880
- templatedDirectories.set(absPath, Date.now());
881
- // Schedule cleanup of this marker after enough time for file events
882
- setTimeout(() => templatedDirectories.delete(absPath), 1000);
883
- // Don't restart - wait for the template files to trigger the rebuild
884
- return;
885
- }
430
+ catch (err) {
431
+ logger.debug('Error killing gravity process on error: %s', err);
886
432
  }
887
- // Check if this file/directory was just templated - skip restart to avoid race condition
888
- for (const [templatedPath, timestamp] of templatedDirectories.entries()) {
889
- if (absPath.startsWith(templatedPath) && Date.now() - timestamp < 500) {
890
- logger.trace('Ignoring event in recently templated directory: %s', templatedPath);
891
- return;
433
+ }
434
+ if (viteServer)
435
+ await viteServer.close();
436
+ // Wait for next restart trigger
437
+ await new Promise((resolve) => {
438
+ const checkRestart = setInterval(() => {
439
+ if (shouldRestart) {
440
+ clearInterval(checkRestart);
441
+ resolve();
892
442
  }
893
- }
894
- logger.trace('File change detected: %s (event: %s, file: %s)', absPath, eventType, changedFile);
895
- restart();
443
+ }, 100);
896
444
  });
897
- watchers.push(watcher);
898
- logger.trace('✓ Watcher added for %s', watchDir);
899
- }
900
- catch (error) {
901
- logger.error('Failed to setup watcher for %s: %s', watchDir, error);
902
445
  }
903
446
  }
904
- logger.debug('Dev server watching for changes');
905
- // Keep the handler alive indefinitely
906
- await new Promise(() => { }).catch(() => cleanup());
907
447
  },
908
448
  });
909
449
  //# sourceMappingURL=index.js.map