@agentuity/cli 0.0.23 → 0.0.25
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.
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cmd/dev/index.ts +198 -15
- package/src/cmd/project/download.ts +28 -0
- package/src/tui.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,mCAuVlB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/download.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAsBD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAyH9E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/download.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAsBD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAyH9E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DvE"}
|
package/package.json
CHANGED
package/src/cmd/dev/index.ts
CHANGED
|
@@ -41,13 +41,42 @@ export const command = createCommand({
|
|
|
41
41
|
const agentuityDir = resolve(rootDir, '.agentuity');
|
|
42
42
|
const appPath = resolve(agentuityDir, 'app.js');
|
|
43
43
|
|
|
44
|
-
|
|
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;
|
|
56
|
+
|
|
57
|
+
// Track restart timestamps to detect restart loops
|
|
58
|
+
const restartTimestamps: number[] = [];
|
|
59
|
+
const MAX_RESTARTS = 10;
|
|
60
|
+
const TIME_WINDOW_MS = 10000; // 10 seconds
|
|
61
|
+
|
|
62
|
+
function checkRestartThrottle() {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
restartTimestamps.push(now);
|
|
65
|
+
|
|
66
|
+
// Remove timestamps older than the time window
|
|
67
|
+
while (restartTimestamps.length > 0 && now - restartTimestamps[0]! > TIME_WINDOW_MS) {
|
|
68
|
+
restartTimestamps.shift();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if we've exceeded the threshold
|
|
72
|
+
if (restartTimestamps.length >= MAX_RESTARTS) {
|
|
73
|
+
tui.error(`Detected ${MAX_RESTARTS} restarts in ${TIME_WINDOW_MS / 1000} seconds`);
|
|
74
|
+
tui.error(
|
|
75
|
+
'This usually indicates a file watcher loop (e.g., log files in the project root)'
|
|
76
|
+
);
|
|
77
|
+
tui.fatal('Too many rapid restarts, exiting to prevent infinite loop');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
51
80
|
|
|
52
81
|
function failure(msg: string) {
|
|
53
82
|
failed = true;
|
|
@@ -60,23 +89,42 @@ export const command = createCommand({
|
|
|
60
89
|
}
|
|
61
90
|
}
|
|
62
91
|
|
|
63
|
-
const kill = () => {
|
|
92
|
+
const kill = async () => {
|
|
93
|
+
if (!running || !devServer) {
|
|
94
|
+
logger.trace('kill() called but server not running');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logger.trace('Killing dev server (pid: %d)', pid);
|
|
99
|
+
shuttingDownForRestart = true;
|
|
64
100
|
running = false;
|
|
65
101
|
try {
|
|
66
102
|
// Kill the process group (negative PID kills entire group)
|
|
67
103
|
process.kill(-pid, 'SIGTERM');
|
|
104
|
+
logger.trace('Sent SIGTERM to process group');
|
|
68
105
|
} catch {
|
|
69
106
|
// Fallback: kill the direct process
|
|
70
107
|
try {
|
|
71
108
|
if (devServer) {
|
|
72
109
|
devServer.kill();
|
|
110
|
+
logger.trace('Killed dev server process directly');
|
|
73
111
|
}
|
|
74
112
|
} catch {
|
|
75
113
|
// Ignore if already dead
|
|
114
|
+
logger.trace('Process already dead');
|
|
76
115
|
}
|
|
77
|
-
} finally {
|
|
78
|
-
devServer = undefined;
|
|
79
116
|
}
|
|
117
|
+
|
|
118
|
+
// Wait for the server to actually exit
|
|
119
|
+
if (exitPromise) {
|
|
120
|
+
logger.trace('Waiting for dev server to exit...');
|
|
121
|
+
await exitPromise;
|
|
122
|
+
logger.trace('Dev server exited');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
devServer = undefined;
|
|
126
|
+
exitPromise = undefined;
|
|
127
|
+
shuttingDownForRestart = false;
|
|
80
128
|
};
|
|
81
129
|
|
|
82
130
|
// Handle signals to ensure entire process tree is killed
|
|
@@ -95,11 +143,27 @@ export const command = createCommand({
|
|
|
95
143
|
process.on('SIGTERM', cleanup);
|
|
96
144
|
|
|
97
145
|
async function restart() {
|
|
146
|
+
// Queue restart if already restarting
|
|
147
|
+
if (restarting) {
|
|
148
|
+
logger.trace('Restart already in progress, queuing another restart');
|
|
149
|
+
pendingRestart = true;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
logger.trace('restart() called, restarting=%s, running=%s', restarting, running);
|
|
154
|
+
restarting = true;
|
|
155
|
+
pendingRestart = false;
|
|
156
|
+
failed = false;
|
|
98
157
|
try {
|
|
99
158
|
if (running) {
|
|
159
|
+
logger.trace('Server is running, killing before restart');
|
|
160
|
+
checkRestartThrottle();
|
|
100
161
|
tui.info('Restarting on file change');
|
|
101
|
-
kill();
|
|
102
|
-
|
|
162
|
+
await kill();
|
|
163
|
+
logger.trace('Server killed, continuing with restart');
|
|
164
|
+
// Continue with restart after kill completes
|
|
165
|
+
} else {
|
|
166
|
+
logger.trace('Initial server start');
|
|
103
167
|
}
|
|
104
168
|
await Promise.all([
|
|
105
169
|
tui.runCommand({
|
|
@@ -132,9 +196,10 @@ export const command = createCommand({
|
|
|
132
196
|
return;
|
|
133
197
|
}
|
|
134
198
|
|
|
199
|
+
logger.trace('Starting dev server: %s', appPath);
|
|
135
200
|
// Use shell to run in a process group for proper cleanup
|
|
136
201
|
// The 'exec' ensures the shell is replaced by the actual process
|
|
137
|
-
|
|
202
|
+
devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], {
|
|
138
203
|
cwd: rootDir,
|
|
139
204
|
stdout: 'inherit',
|
|
140
205
|
stderr: 'inherit',
|
|
@@ -144,25 +209,143 @@ export const command = createCommand({
|
|
|
144
209
|
running = true;
|
|
145
210
|
failed = false;
|
|
146
211
|
pid = devServer.pid;
|
|
212
|
+
exitPromise = devServer.exited;
|
|
213
|
+
logger.trace('Dev server started (pid: %d)', pid);
|
|
147
214
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
215
|
+
// Attach non-blocking exit handler
|
|
216
|
+
exitPromise
|
|
217
|
+
.then((exitCode) => {
|
|
218
|
+
logger.trace(
|
|
219
|
+
'Dev server exited with code %d (shuttingDownForRestart=%s)',
|
|
220
|
+
exitCode,
|
|
221
|
+
shuttingDownForRestart
|
|
222
|
+
);
|
|
223
|
+
running = false;
|
|
224
|
+
devServer = undefined;
|
|
225
|
+
exitPromise = undefined;
|
|
226
|
+
// Only exit the CLI if this is a clean exit AND not a restart
|
|
227
|
+
if (exitCode === 0 && !shuttingDownForRestart) {
|
|
228
|
+
logger.trace('Clean exit, stopping CLI');
|
|
229
|
+
process.exit(exitCode);
|
|
230
|
+
}
|
|
231
|
+
// Non-zero exit codes are treated as restartable failures
|
|
232
|
+
})
|
|
233
|
+
.catch((error) => {
|
|
234
|
+
logger.trace(
|
|
235
|
+
'Dev server exit error (shuttingDownForRestart=%s): %s',
|
|
236
|
+
shuttingDownForRestart,
|
|
237
|
+
error
|
|
238
|
+
);
|
|
239
|
+
running = false;
|
|
240
|
+
devServer = undefined;
|
|
241
|
+
exitPromise = undefined;
|
|
242
|
+
if (!shuttingDownForRestart) {
|
|
243
|
+
if (error instanceof Error) {
|
|
244
|
+
failure(`Dev server failed: ${error.message}`);
|
|
245
|
+
} else {
|
|
246
|
+
failure('Dev server failed');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
152
250
|
} catch (error) {
|
|
153
251
|
if (error instanceof Error) {
|
|
154
252
|
failure(`Dev server failed: ${error.message}`);
|
|
155
253
|
} else {
|
|
156
254
|
failure('Dev server failed');
|
|
157
255
|
}
|
|
158
|
-
} finally {
|
|
159
256
|
running = false;
|
|
257
|
+
devServer = undefined;
|
|
258
|
+
} finally {
|
|
259
|
+
const hadPendingRestart = pendingRestart;
|
|
260
|
+
restarting = false;
|
|
261
|
+
pendingRestart = false;
|
|
262
|
+
logger.trace(
|
|
263
|
+
'restart() completed, restarting=%s, hadPendingRestart=%s',
|
|
264
|
+
restarting,
|
|
265
|
+
hadPendingRestart
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// If another restart was queued while we were restarting, trigger it now
|
|
269
|
+
if (hadPendingRestart) {
|
|
270
|
+
logger.trace('Triggering queued restart');
|
|
271
|
+
setImmediate(restart);
|
|
272
|
+
}
|
|
160
273
|
}
|
|
161
274
|
}
|
|
275
|
+
|
|
276
|
+
logger.trace('Starting initial build and server');
|
|
162
277
|
await restart();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
278
|
+
logger.trace('Initial restart completed, setting up watchers');
|
|
279
|
+
|
|
280
|
+
// Patterns to ignore (generated files that change during build)
|
|
281
|
+
const ignorePatterns = [
|
|
282
|
+
/\.generated\.(js|ts|d\.ts)$/,
|
|
283
|
+
/registry\.generated\.ts$/,
|
|
284
|
+
/types\.generated\.d\.ts$/,
|
|
285
|
+
/client\.generated\.js$/,
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
logger.trace('Setting up file watchers for: %s', watches.join(', '));
|
|
289
|
+
for (const watchDir of watches) {
|
|
290
|
+
try {
|
|
291
|
+
logger.trace('Setting up watcher for %s', watchDir);
|
|
292
|
+
const watcher = watch(watchDir, { recursive: true }, (eventType, changedFile) => {
|
|
293
|
+
const absPath = changedFile ? join(watchDir, changedFile) : watchDir;
|
|
294
|
+
|
|
295
|
+
// Ignore node_modules folder
|
|
296
|
+
if (absPath.includes('node_modules')) {
|
|
297
|
+
logger.trace(
|
|
298
|
+
'File change ignored (node_modules): %s (event: %s, file: %s)',
|
|
299
|
+
watchDir,
|
|
300
|
+
eventType,
|
|
301
|
+
changedFile
|
|
302
|
+
);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Ignore changes in .agentuity directory (build output)
|
|
307
|
+
if (absPath.startsWith(agentuityDir)) {
|
|
308
|
+
logger.trace(
|
|
309
|
+
'File change ignored (.agentuity dir): %s (event: %s, file: %s)',
|
|
310
|
+
watchDir,
|
|
311
|
+
eventType,
|
|
312
|
+
changedFile
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Ignore generated files to prevent restart loops
|
|
318
|
+
if (changedFile) {
|
|
319
|
+
for (const pattern of ignorePatterns) {
|
|
320
|
+
if (pattern.test(changedFile)) {
|
|
321
|
+
logger.trace(
|
|
322
|
+
'File change ignored (generated file): %s (event: %s, file: %s)',
|
|
323
|
+
watchDir,
|
|
324
|
+
eventType,
|
|
325
|
+
changedFile
|
|
326
|
+
);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
logger.trace(
|
|
333
|
+
'File change detected: %s (event: %s, file: %s)',
|
|
334
|
+
absPath,
|
|
335
|
+
eventType,
|
|
336
|
+
changedFile
|
|
337
|
+
);
|
|
338
|
+
restart();
|
|
339
|
+
});
|
|
340
|
+
watchers.push(watcher);
|
|
341
|
+
logger.trace('✓ Watcher added for %s', watchDir);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
logger.error('Failed to setup watcher for %s: %s', watchDir, error);
|
|
344
|
+
}
|
|
166
345
|
}
|
|
346
|
+
logger.debug('Dev server watching for changes');
|
|
347
|
+
|
|
348
|
+
// Keep the handler alive indefinitely
|
|
349
|
+
await new Promise(() => {});
|
|
167
350
|
},
|
|
168
351
|
});
|
|
@@ -213,6 +213,34 @@ export async function setupProject(options: SetupOptions): Promise<void> {
|
|
|
213
213
|
logger.error('Failed to build project');
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
+
|
|
217
|
+
// Initialize git repository if git is available
|
|
218
|
+
const gitPath = Bun.which('git');
|
|
219
|
+
if (gitPath) {
|
|
220
|
+
// Git is available, initialize repository
|
|
221
|
+
await tui.runCommand({
|
|
222
|
+
command: 'git init',
|
|
223
|
+
cwd: dest,
|
|
224
|
+
cmd: ['git', 'init'],
|
|
225
|
+
clearOnSuccess: true,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Add all files
|
|
229
|
+
await tui.runCommand({
|
|
230
|
+
command: 'git add .',
|
|
231
|
+
cwd: dest,
|
|
232
|
+
cmd: ['git', 'add', '.'],
|
|
233
|
+
clearOnSuccess: true,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Create initial commit
|
|
237
|
+
await tui.runCommand({
|
|
238
|
+
command: 'git commit -m "Initial Setup"',
|
|
239
|
+
cwd: dest,
|
|
240
|
+
cmd: ['git', 'commit', '-m', 'Initial Setup'],
|
|
241
|
+
clearOnSuccess: true,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
216
244
|
}
|
|
217
245
|
|
|
218
246
|
async function replaceInFiles(dir: string, projectName: string, dirName: string): Promise<void> {
|
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(
|
|
893
|
+
renderOutput(maxLinesOutput); // Show last N lines while streaming
|
|
894
894
|
}
|
|
895
895
|
}
|
|
896
896
|
}
|