@agentuity/cli
Advanced tools
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,mCAgKlB,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,mCA8TlB,CAAC"} |
+1
-1
| { | ||
| "name": "@agentuity/cli", | ||
| "version": "0.0.23", | ||
| "version": "0.0.24", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "main": "./src/index.ts", |
+173
-15
@@ -44,3 +44,4 @@ import { createCommand } from '../../types'; | ||
| const watches = [appTs, srcDir]; | ||
| // Watch directories instead of files to survive atomic replacements (sed -i, cp) | ||
| const watches = [rootDir]; | ||
| const watchers: FSWatcher[] = []; | ||
@@ -52,2 +53,6 @@ let failures = 0; | ||
| let devServer: Bun.Subprocess | undefined; | ||
| let exitPromise: Promise<number> | undefined; | ||
| let restarting = false; | ||
| let shuttingDownForRestart = false; | ||
| let pendingRestart = false; | ||
@@ -65,3 +70,10 @@ function failure(msg: string) { | ||
| const kill = () => { | ||
| const kill = async () => { | ||
| if (!running || !devServer) { | ||
| logger.trace('kill() called but server not running'); | ||
| return; | ||
| } | ||
| logger.trace('Killing dev server (pid: %d)', pid); | ||
| shuttingDownForRestart = true; | ||
| running = false; | ||
@@ -71,2 +83,3 @@ try { | ||
| process.kill(-pid, 'SIGTERM'); | ||
| logger.trace('Sent SIGTERM to process group'); | ||
| } catch { | ||
@@ -77,9 +90,20 @@ // Fallback: kill the direct process | ||
| devServer.kill(); | ||
| logger.trace('Killed dev server process directly'); | ||
| } | ||
| } catch { | ||
| // Ignore if already dead | ||
| logger.trace('Process already dead'); | ||
| } | ||
| } finally { | ||
| devServer = undefined; | ||
| } | ||
| // Wait for the server to actually exit | ||
| if (exitPromise) { | ||
| logger.trace('Waiting for dev server to exit...'); | ||
| await exitPromise; | ||
| logger.trace('Dev server exited'); | ||
| } | ||
| devServer = undefined; | ||
| exitPromise = undefined; | ||
| shuttingDownForRestart = false; | ||
| }; | ||
@@ -103,7 +127,22 @@ | ||
| async function restart() { | ||
| // Queue restart if already restarting | ||
| if (restarting) { | ||
| logger.trace('Restart already in progress, queuing another restart'); | ||
| pendingRestart = true; | ||
| return; | ||
| } | ||
| logger.trace('restart() called, restarting=%s, running=%s', restarting, running); | ||
| restarting = true; | ||
| pendingRestart = false; | ||
| failed = false; | ||
| try { | ||
| if (running) { | ||
| logger.trace('Server is running, killing before restart'); | ||
| tui.info('Restarting on file change'); | ||
| kill(); | ||
| return; | ||
| await kill(); | ||
| logger.trace('Server killed, continuing with restart'); | ||
| // Continue with restart after kill completes | ||
| } else { | ||
| logger.trace('Initial server start'); | ||
| } | ||
@@ -141,5 +180,6 @@ await Promise.all([ | ||
| logger.trace('Starting dev server: %s', appPath); | ||
| // Use shell to run in a process group for proper cleanup | ||
| // The 'exec' ensures the shell is replaced by the actual process | ||
| const devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], { | ||
| devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], { | ||
| cwd: rootDir, | ||
@@ -154,7 +194,40 @@ stdout: 'inherit', | ||
| pid = devServer.pid; | ||
| exitPromise = devServer.exited; | ||
| logger.trace('Dev server started (pid: %d)', pid); | ||
| const exitCode = await devServer.exited; | ||
| if (exitCode === 0) { | ||
| process.exit(exitCode); | ||
| } | ||
| // Attach non-blocking exit handler | ||
| exitPromise | ||
| .then((exitCode) => { | ||
| logger.trace( | ||
| 'Dev server exited with code %d (shuttingDownForRestart=%s)', | ||
| exitCode, | ||
| shuttingDownForRestart | ||
| ); | ||
| running = false; | ||
| devServer = undefined; | ||
| exitPromise = undefined; | ||
| // Only exit the CLI if this is a clean exit AND not a restart | ||
| if (exitCode === 0 && !shuttingDownForRestart) { | ||
| logger.trace('Clean exit, stopping CLI'); | ||
| process.exit(exitCode); | ||
| } | ||
| // Non-zero exit codes are treated as restartable failures | ||
| }) | ||
| .catch((error) => { | ||
| logger.trace( | ||
| 'Dev server exit error (shuttingDownForRestart=%s): %s', | ||
| shuttingDownForRestart, | ||
| error | ||
| ); | ||
| running = false; | ||
| devServer = undefined; | ||
| exitPromise = undefined; | ||
| if (!shuttingDownForRestart) { | ||
| if (error instanceof Error) { | ||
| failure(`Dev server failed: ${error.message}`); | ||
| } else { | ||
| failure('Dev server failed'); | ||
| } | ||
| } | ||
| }); | ||
| } catch (error) { | ||
@@ -166,12 +239,97 @@ if (error instanceof Error) { | ||
| } | ||
| running = false; | ||
| devServer = undefined; | ||
| } finally { | ||
| running = false; | ||
| const hadPendingRestart = pendingRestart; | ||
| restarting = false; | ||
| pendingRestart = false; | ||
| logger.trace( | ||
| 'restart() completed, restarting=%s, hadPendingRestart=%s', | ||
| restarting, | ||
| hadPendingRestart | ||
| ); | ||
| // If another restart was queued while we were restarting, trigger it now | ||
| if (hadPendingRestart) { | ||
| logger.trace('Triggering queued restart'); | ||
| setImmediate(restart); | ||
| } | ||
| } | ||
| } | ||
| logger.trace('Starting initial build and server'); | ||
| await restart(); | ||
| for (const filename of watches) { | ||
| logger.trace('watching %s', filename); | ||
| watchers.push(watch(filename, { recursive: true }, restart)); | ||
| logger.trace('Initial restart completed, setting up watchers'); | ||
| // Patterns to ignore (generated files that change during build) | ||
| const ignorePatterns = [ | ||
| /\.generated\.(js|ts|d\.ts)$/, | ||
| /registry\.generated\.ts$/, | ||
| /types\.generated\.d\.ts$/, | ||
| /client\.generated\.js$/, | ||
| ]; | ||
| logger.trace('Setting up file watchers for: %s', watches.join(', ')); | ||
| for (const watchDir of watches) { | ||
| try { | ||
| logger.trace('Setting up watcher for %s', watchDir); | ||
| const watcher = watch(watchDir, { recursive: true }, (eventType, changedFile) => { | ||
| const absPath = changedFile ? join(watchDir, changedFile) : watchDir; | ||
| // Ignore node_modules folder | ||
| if (absPath.includes('node_modules')) { | ||
| logger.trace( | ||
| 'File change ignored (node_modules): %s (event: %s, file: %s)', | ||
| watchDir, | ||
| eventType, | ||
| changedFile | ||
| ); | ||
| return; | ||
| } | ||
| // Ignore changes in .agentuity directory (build output) | ||
| if (absPath.startsWith(agentuityDir)) { | ||
| logger.trace( | ||
| 'File change ignored (.agentuity dir): %s (event: %s, file: %s)', | ||
| watchDir, | ||
| eventType, | ||
| changedFile | ||
| ); | ||
| return; | ||
| } | ||
| // Ignore generated files to prevent restart loops | ||
| if (changedFile) { | ||
| for (const pattern of ignorePatterns) { | ||
| if (pattern.test(changedFile)) { | ||
| logger.trace( | ||
| 'File change ignored (generated file): %s (event: %s, file: %s)', | ||
| watchDir, | ||
| eventType, | ||
| changedFile | ||
| ); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| logger.trace( | ||
| 'File change detected: %s (event: %s, file: %s)', | ||
| absPath, | ||
| eventType, | ||
| changedFile | ||
| ); | ||
| restart(); | ||
| }); | ||
| watchers.push(watcher); | ||
| logger.trace('✓ Watcher added for %s', watchDir); | ||
| } catch (error) { | ||
| logger.error('Failed to setup watcher for %s: %s', watchDir, error); | ||
| } | ||
| } | ||
| logger.debug('Dev server watching for changes'); | ||
| // Keep the handler alive indefinitely | ||
| await new Promise(() => {}); | ||
| }, | ||
| }); |
+1
-1
@@ -893,3 +893,3 @@ /** | ||
| allOutputLines.push(line); | ||
| renderOutput(3); // Show last 3 lines while streaming | ||
| renderOutput(maxLinesOutput); // Show last N lines while streaming | ||
| } | ||
@@ -896,0 +896,0 @@ } |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 16 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 16 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
252219
1.95%6034
2.46%