@agentuity/cli 0.0.22 → 0.0.24

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,mCAgKlB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,mCA8TlB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentuity/cli",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -41,13 +41,18 @@ export const command = createCommand({
41
41
  const agentuityDir = resolve(rootDir, '.agentuity');
42
42
  const appPath = resolve(agentuityDir, 'app.js');
43
43
 
44
- const watches = [appTs, srcDir];
44
+ // Watch directories instead of files to survive atomic replacements (sed -i, cp)
45
+ const watches = [rootDir];
45
46
  const watchers: FSWatcher[] = [];
46
47
  let failures = 0;
47
48
  let running = false;
48
49
  let pid = 0;
49
50
  let failed = false;
50
51
  let devServer: Bun.Subprocess | undefined;
52
+ let exitPromise: Promise<number> | undefined;
53
+ let restarting = false;
54
+ let shuttingDownForRestart = false;
55
+ let pendingRestart = false;
51
56
 
52
57
  function failure(msg: string) {
53
58
  failed = true;
@@ -60,23 +65,42 @@ export const command = createCommand({
60
65
  }
61
66
  }
62
67
 
63
- const kill = () => {
68
+ const kill = async () => {
69
+ if (!running || !devServer) {
70
+ logger.trace('kill() called but server not running');
71
+ return;
72
+ }
73
+
74
+ logger.trace('Killing dev server (pid: %d)', pid);
75
+ shuttingDownForRestart = true;
64
76
  running = false;
65
77
  try {
66
78
  // Kill the process group (negative PID kills entire group)
67
79
  process.kill(-pid, 'SIGTERM');
80
+ logger.trace('Sent SIGTERM to process group');
68
81
  } catch {
69
82
  // Fallback: kill the direct process
70
83
  try {
71
84
  if (devServer) {
72
85
  devServer.kill();
86
+ logger.trace('Killed dev server process directly');
73
87
  }
74
88
  } catch {
75
89
  // Ignore if already dead
90
+ logger.trace('Process already dead');
76
91
  }
77
- } finally {
78
- devServer = undefined;
79
92
  }
93
+
94
+ // Wait for the server to actually exit
95
+ if (exitPromise) {
96
+ logger.trace('Waiting for dev server to exit...');
97
+ await exitPromise;
98
+ logger.trace('Dev server exited');
99
+ }
100
+
101
+ devServer = undefined;
102
+ exitPromise = undefined;
103
+ shuttingDownForRestart = false;
80
104
  };
81
105
 
82
106
  // Handle signals to ensure entire process tree is killed
@@ -95,11 +119,26 @@ export const command = createCommand({
95
119
  process.on('SIGTERM', cleanup);
96
120
 
97
121
  async function restart() {
122
+ // Queue restart if already restarting
123
+ if (restarting) {
124
+ logger.trace('Restart already in progress, queuing another restart');
125
+ pendingRestart = true;
126
+ return;
127
+ }
128
+
129
+ logger.trace('restart() called, restarting=%s, running=%s', restarting, running);
130
+ restarting = true;
131
+ pendingRestart = false;
132
+ failed = false;
98
133
  try {
99
134
  if (running) {
135
+ logger.trace('Server is running, killing before restart');
100
136
  tui.info('Restarting on file change');
101
- kill();
102
- return;
137
+ await kill();
138
+ logger.trace('Server killed, continuing with restart');
139
+ // Continue with restart after kill completes
140
+ } else {
141
+ logger.trace('Initial server start');
103
142
  }
104
143
  await Promise.all([
105
144
  tui.runCommand({
@@ -108,7 +147,7 @@ export const command = createCommand({
108
147
  cwd: rootDir,
109
148
  clearOnSuccess: true,
110
149
  truncate: false,
111
- maxLinesOutput: 1,
150
+ maxLinesOutput: 2,
112
151
  maxLinesOnFailure: 15,
113
152
  }),
114
153
  tui.spinner('Building project', async () => {
@@ -132,9 +171,10 @@ export const command = createCommand({
132
171
  return;
133
172
  }
134
173
 
174
+ logger.trace('Starting dev server: %s', appPath);
135
175
  // Use shell to run in a process group for proper cleanup
136
176
  // The 'exec' ensures the shell is replaced by the actual process
137
- const devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], {
177
+ devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], {
138
178
  cwd: rootDir,
139
179
  stdout: 'inherit',
140
180
  stderr: 'inherit',
@@ -144,25 +184,143 @@ export const command = createCommand({
144
184
  running = true;
145
185
  failed = false;
146
186
  pid = devServer.pid;
187
+ exitPromise = devServer.exited;
188
+ logger.trace('Dev server started (pid: %d)', pid);
147
189
 
148
- const exitCode = await devServer.exited;
149
- if (exitCode === 0) {
150
- process.exit(exitCode);
151
- }
190
+ // Attach non-blocking exit handler
191
+ exitPromise
192
+ .then((exitCode) => {
193
+ logger.trace(
194
+ 'Dev server exited with code %d (shuttingDownForRestart=%s)',
195
+ exitCode,
196
+ shuttingDownForRestart
197
+ );
198
+ running = false;
199
+ devServer = undefined;
200
+ exitPromise = undefined;
201
+ // Only exit the CLI if this is a clean exit AND not a restart
202
+ if (exitCode === 0 && !shuttingDownForRestart) {
203
+ logger.trace('Clean exit, stopping CLI');
204
+ process.exit(exitCode);
205
+ }
206
+ // Non-zero exit codes are treated as restartable failures
207
+ })
208
+ .catch((error) => {
209
+ logger.trace(
210
+ 'Dev server exit error (shuttingDownForRestart=%s): %s',
211
+ shuttingDownForRestart,
212
+ error
213
+ );
214
+ running = false;
215
+ devServer = undefined;
216
+ exitPromise = undefined;
217
+ if (!shuttingDownForRestart) {
218
+ if (error instanceof Error) {
219
+ failure(`Dev server failed: ${error.message}`);
220
+ } else {
221
+ failure('Dev server failed');
222
+ }
223
+ }
224
+ });
152
225
  } catch (error) {
153
226
  if (error instanceof Error) {
154
227
  failure(`Dev server failed: ${error.message}`);
155
228
  } else {
156
229
  failure('Dev server failed');
157
230
  }
158
- } finally {
159
231
  running = false;
232
+ devServer = undefined;
233
+ } finally {
234
+ const hadPendingRestart = pendingRestart;
235
+ restarting = false;
236
+ pendingRestart = false;
237
+ logger.trace(
238
+ 'restart() completed, restarting=%s, hadPendingRestart=%s',
239
+ restarting,
240
+ hadPendingRestart
241
+ );
242
+
243
+ // If another restart was queued while we were restarting, trigger it now
244
+ if (hadPendingRestart) {
245
+ logger.trace('Triggering queued restart');
246
+ setImmediate(restart);
247
+ }
160
248
  }
161
249
  }
250
+
251
+ logger.trace('Starting initial build and server');
162
252
  await restart();
163
- for (const filename of watches) {
164
- logger.trace('watching %s', filename);
165
- watchers.push(watch(filename, { recursive: true }, restart));
253
+ logger.trace('Initial restart completed, setting up watchers');
254
+
255
+ // Patterns to ignore (generated files that change during build)
256
+ const ignorePatterns = [
257
+ /\.generated\.(js|ts|d\.ts)$/,
258
+ /registry\.generated\.ts$/,
259
+ /types\.generated\.d\.ts$/,
260
+ /client\.generated\.js$/,
261
+ ];
262
+
263
+ logger.trace('Setting up file watchers for: %s', watches.join(', '));
264
+ for (const watchDir of watches) {
265
+ try {
266
+ logger.trace('Setting up watcher for %s', watchDir);
267
+ const watcher = watch(watchDir, { recursive: true }, (eventType, changedFile) => {
268
+ const absPath = changedFile ? join(watchDir, changedFile) : watchDir;
269
+
270
+ // Ignore node_modules folder
271
+ if (absPath.includes('node_modules')) {
272
+ logger.trace(
273
+ 'File change ignored (node_modules): %s (event: %s, file: %s)',
274
+ watchDir,
275
+ eventType,
276
+ changedFile
277
+ );
278
+ return;
279
+ }
280
+
281
+ // Ignore changes in .agentuity directory (build output)
282
+ if (absPath.startsWith(agentuityDir)) {
283
+ logger.trace(
284
+ 'File change ignored (.agentuity dir): %s (event: %s, file: %s)',
285
+ watchDir,
286
+ eventType,
287
+ changedFile
288
+ );
289
+ return;
290
+ }
291
+
292
+ // Ignore generated files to prevent restart loops
293
+ if (changedFile) {
294
+ for (const pattern of ignorePatterns) {
295
+ if (pattern.test(changedFile)) {
296
+ logger.trace(
297
+ 'File change ignored (generated file): %s (event: %s, file: %s)',
298
+ watchDir,
299
+ eventType,
300
+ changedFile
301
+ );
302
+ return;
303
+ }
304
+ }
305
+ }
306
+
307
+ logger.trace(
308
+ 'File change detected: %s (event: %s, file: %s)',
309
+ absPath,
310
+ eventType,
311
+ changedFile
312
+ );
313
+ restart();
314
+ });
315
+ watchers.push(watcher);
316
+ logger.trace('✓ Watcher added for %s', watchDir);
317
+ } catch (error) {
318
+ logger.error('Failed to setup watcher for %s: %s', watchDir, error);
319
+ }
166
320
  }
321
+ logger.debug('Dev server watching for changes');
322
+
323
+ // Keep the handler alive indefinitely
324
+ await new Promise(() => {});
167
325
  },
168
326
  });
package/src/tui.ts CHANGED
@@ -890,7 +890,7 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
890
890
  for (const line of lines) {
891
891
  if (line.trim()) {
892
892
  allOutputLines.push(line);
893
- renderOutput(3); // Show last 3 lines while streaming
893
+ renderOutput(maxLinesOutput); // Show last N lines while streaming
894
894
  }
895
895
  }
896
896
  }