@agentuity/cli 1.0.47 → 2.0.0-beta.0
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/build/app-router-detector.d.ts +2 -5
- package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
- package/dist/cmd/build/app-router-detector.js +130 -154
- package/dist/cmd/build/app-router-detector.js.map +1 -1
- package/dist/cmd/build/ids.d.ts +11 -0
- package/dist/cmd/build/ids.d.ts.map +1 -0
- package/dist/cmd/build/ids.js +18 -0
- package/dist/cmd/build/ids.js.map +1 -0
- package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +166 -487
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +67 -134
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/docs-generator.js +0 -2
- package/dist/cmd/build/vite/docs-generator.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +0 -36
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
- package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
- package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +97 -177
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/server-bundler.js +1 -1
- package/dist/cmd/build/vite/server-bundler.js.map +1 -1
- package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
- package/dist/cmd/build/vite/static-renderer.js +1 -9
- package/dist/cmd/build/vite/static-renderer.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +171 -18
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +14 -13
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +6 -34
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
- package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
- package/dist/cmd/build/vite/ws-proxy.js +95 -0
- package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
- package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite-bundler.js +0 -3
- package/dist/cmd/build/vite-bundler.js.map +1 -1
- package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
- package/dist/cmd/dev/file-watcher.js +2 -8
- package/dist/cmd/dev/file-watcher.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +369 -720
- package/dist/cmd/dev/index.js.map +1 -1
- package/package.json +6 -8
- package/src/cmd/ai/prompt/agent.md +0 -1
- package/src/cmd/ai/prompt/api.md +0 -7
- package/src/cmd/ai/prompt/web.md +51 -213
- package/src/cmd/build/app-router-detector.ts +152 -182
- package/src/cmd/build/ids.ts +19 -0
- package/src/cmd/build/vite/agent-discovery.ts +208 -679
- package/src/cmd/build/vite/bun-dev-server.ts +78 -154
- package/src/cmd/build/vite/docs-generator.ts +0 -2
- package/src/cmd/build/vite/index.ts +1 -42
- package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
- package/src/cmd/build/vite/route-discovery.ts +116 -274
- package/src/cmd/build/vite/server-bundler.ts +1 -1
- package/src/cmd/build/vite/static-renderer.ts +1 -11
- package/src/cmd/build/vite/vite-asset-server-config.ts +196 -20
- package/src/cmd/build/vite/vite-asset-server.ts +25 -15
- package/src/cmd/build/vite/vite-builder.ts +6 -51
- package/src/cmd/build/vite/ws-proxy.ts +126 -0
- package/src/cmd/build/vite-bundler.ts +0 -4
- package/src/cmd/dev/file-watcher.ts +2 -9
- package/src/cmd/dev/index.ts +409 -832
- package/dist/cmd/build/ast.d.ts +0 -78
- package/dist/cmd/build/ast.d.ts.map +0 -1
- package/dist/cmd/build/ast.js +0 -2703
- package/dist/cmd/build/ast.js.map +0 -1
- package/dist/cmd/build/entry-generator.d.ts +0 -25
- package/dist/cmd/build/entry-generator.d.ts.map +0 -1
- package/dist/cmd/build/entry-generator.js +0 -695
- package/dist/cmd/build/entry-generator.js.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
- package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.js +0 -83
- package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
- package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
- package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
- package/dist/cmd/build/vite/registry-generator.js +0 -1108
- package/dist/cmd/build/vite/registry-generator.js.map +0 -1
- package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
- package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
- package/dist/cmd/build/webanalytics-generator.js +0 -178
- package/dist/cmd/build/webanalytics-generator.js.map +0 -1
- package/dist/cmd/build/workbench.d.ts +0 -7
- package/dist/cmd/build/workbench.d.ts.map +0 -1
- package/dist/cmd/build/workbench.js +0 -55
- package/dist/cmd/build/workbench.js.map +0 -1
- package/dist/utils/route-migration.d.ts +0 -62
- package/dist/utils/route-migration.d.ts.map +0 -1
- package/dist/utils/route-migration.js +0 -630
- package/dist/utils/route-migration.js.map +0 -1
- package/src/cmd/build/ast.ts +0 -3529
- package/src/cmd/build/entry-generator.ts +0 -760
- package/src/cmd/build/vite/api-mount-path.ts +0 -87
- package/src/cmd/build/vite/registry-generator.ts +0 -1267
- package/src/cmd/build/webanalytics-generator.ts +0 -197
- package/src/cmd/build/workbench.ts +0 -58
- package/src/utils/route-migration.ts +0 -757
package/dist/cmd/dev/index.js
CHANGED
|
@@ -11,15 +11,13 @@ import { generateEndpoint } from './api';
|
|
|
11
11
|
import { APIClient, getAPIBaseURL, getAppBaseURL, getGravityDevModeURL } from '../../api';
|
|
12
12
|
import { download } from './download';
|
|
13
13
|
import { createDevmodeSyncService } from './sync';
|
|
14
|
-
import { getDevmodeDeploymentId } from '../build/
|
|
14
|
+
import { getDevmodeDeploymentId } from '../build/ids';
|
|
15
15
|
import { getDefaultConfigDir, saveConfig, loadProjectSDKKey, getAuth } from '../../config';
|
|
16
16
|
import { typecheck } from '../build/typecheck';
|
|
17
17
|
import { validateGravityRequiresUpgrade } from '../../runtime';
|
|
18
18
|
import { isTTY, hasLoggedInBefore } from '../../auth';
|
|
19
|
-
import { createFileWatcher } from './file-watcher';
|
|
20
19
|
import { prepareDevLock, releaseLockSync } from './dev-lock';
|
|
21
20
|
import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
|
|
22
|
-
import { promptRouteMigration, performMigration, checkMigrationEligibility, } from '../../utils/route-migration';
|
|
23
21
|
import { ErrorCode } from '../../errors';
|
|
24
22
|
const DEFAULT_PORT = 3500;
|
|
25
23
|
const MIN_PORT = 1024;
|
|
@@ -56,85 +54,22 @@ async function killLingeringGravityProcesses(logger) {
|
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Waits for the port to become available before returning (with timeout).
|
|
61
|
-
* Handles both in-process server and subprocess (when debugger is enabled).
|
|
57
|
+
* Kill the Bun backend subprocess if one is running.
|
|
62
58
|
*/
|
|
63
|
-
|
|
59
|
+
function killBunSubprocess(logger) {
|
|
64
60
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
61
|
const globalAny = globalThis;
|
|
66
|
-
// Check for subprocess first (used when debugger flags are enabled)
|
|
67
62
|
const bunSubprocess = globalAny.__AGENTUITY_BUN_SUBPROCESS__;
|
|
68
|
-
if (bunSubprocess)
|
|
69
|
-
logger.debug('Stopping Bun subprocess...');
|
|
70
|
-
try {
|
|
71
|
-
bunSubprocess.kill('SIGTERM');
|
|
72
|
-
// After SIGTERM, wait and check multiple times before giving up
|
|
73
|
-
let attempts = 0;
|
|
74
|
-
while (bunSubprocess.exitCode === null && attempts < 3) {
|
|
75
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
76
|
-
attempts++;
|
|
77
|
-
}
|
|
78
|
-
if (bunSubprocess.exitCode === null) {
|
|
79
|
-
bunSubprocess.kill('SIGKILL');
|
|
80
|
-
}
|
|
81
|
-
logger.debug('Bun subprocess killed');
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
logger.debug('Error killing Bun subprocess: %s', err);
|
|
85
|
-
}
|
|
86
|
-
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
87
|
-
// Wait for port to become available
|
|
88
|
-
const MAX_WAIT_ITERATIONS = 10;
|
|
89
|
-
for (let i = 0; i < MAX_WAIT_ITERATIONS; i++) {
|
|
90
|
-
try {
|
|
91
|
-
await fetch(`http://127.0.0.1:${port}/`, {
|
|
92
|
-
method: 'HEAD',
|
|
93
|
-
signal: AbortSignal.timeout(150),
|
|
94
|
-
});
|
|
95
|
-
// Still responding, wait a bit more
|
|
96
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Connection refused or timeout => server is down
|
|
100
|
-
logger.debug('Bun subprocess stopped');
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
63
|
+
if (!bunSubprocess)
|
|
104
64
|
return;
|
|
105
|
-
}
|
|
106
|
-
// Handle in-process server
|
|
107
|
-
const server = globalAny.__AGENTUITY_SERVER__;
|
|
108
|
-
if (!server) {
|
|
109
|
-
logger.debug('No Bun server to stop');
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
65
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
logger.debug('Bun server stop() called');
|
|
66
|
+
bunSubprocess.kill('SIGTERM');
|
|
67
|
+
logger.debug('Bun subprocess killed');
|
|
116
68
|
}
|
|
117
69
|
catch (err) {
|
|
118
|
-
logger.debug('Error
|
|
119
|
-
}
|
|
120
|
-
// Wait for socket to close (max 2 seconds to avoid hanging on shutdown)
|
|
121
|
-
const MAX_WAIT_ITERATIONS = 10;
|
|
122
|
-
for (let i = 0; i < MAX_WAIT_ITERATIONS; i++) {
|
|
123
|
-
try {
|
|
124
|
-
await fetch(`http://127.0.0.1:${port}/`, {
|
|
125
|
-
method: 'HEAD',
|
|
126
|
-
signal: AbortSignal.timeout(150),
|
|
127
|
-
});
|
|
128
|
-
// Still responding, wait a bit more
|
|
129
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
// Connection refused or timeout => server is down
|
|
133
|
-
logger.debug('Bun server stopped');
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
70
|
+
logger.debug('Error killing Bun subprocess: %s', err);
|
|
136
71
|
}
|
|
137
|
-
globalAny.
|
|
72
|
+
globalAny.__AGENTUITY_BUN_SUBPROCESS__ = undefined;
|
|
138
73
|
}
|
|
139
74
|
const getDefaultPort = () => {
|
|
140
75
|
const envPort = process.env.PORT;
|
|
@@ -192,18 +127,10 @@ export const command = createCommand({
|
|
|
192
127
|
.boolean()
|
|
193
128
|
.optional()
|
|
194
129
|
.describe('Enable bun debugger with breakpoint at first line'),
|
|
195
|
-
experimentalNoBundle: z
|
|
196
|
-
.boolean()
|
|
197
|
-
.optional()
|
|
198
|
-
.describe('[Experimental] Skip Bun.build in dev mode — run generated entry file directly'),
|
|
199
130
|
noTypecheck: z
|
|
200
131
|
.boolean()
|
|
201
132
|
.optional()
|
|
202
133
|
.describe('Skip TypeScript type checking on startup and restarts'),
|
|
203
|
-
migrateRoutes: z
|
|
204
|
-
.boolean()
|
|
205
|
-
.optional()
|
|
206
|
-
.describe('Migrate file-based routes to explicit routing (src/api/index.ts root router)'),
|
|
207
134
|
resume: z.string().optional().describe('Resume a paused Hub session by ID'),
|
|
208
135
|
}),
|
|
209
136
|
},
|
|
@@ -357,31 +284,6 @@ export const command = createCommand({
|
|
|
357
284
|
devLock.release();
|
|
358
285
|
tui.fatal(`Failed to upgrade dependencies: ${upgradeResult.failed.join(', ')}`, ErrorCode.BUILD_FAILED);
|
|
359
286
|
}
|
|
360
|
-
// Check if project can migrate to explicit routing
|
|
361
|
-
if (opts.migrateRoutes) {
|
|
362
|
-
const eligibility = checkMigrationEligibility(rootDir);
|
|
363
|
-
if (eligibility.available) {
|
|
364
|
-
const result = performMigration(rootDir, eligibility.routeFiles);
|
|
365
|
-
if (result.success) {
|
|
366
|
-
tui.success(result.message);
|
|
367
|
-
if (result.filesCreated.length > 0) {
|
|
368
|
-
tui.info(`Created: ${result.filesCreated.map((f) => tui.muted(f)).join(', ')}`);
|
|
369
|
-
}
|
|
370
|
-
tui.newline();
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
tui.warning(result.message);
|
|
374
|
-
tui.newline();
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
tui.info('No migration needed — already using explicit routing.');
|
|
379
|
-
tui.newline();
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
await promptRouteMigration(rootDir, logger, { interactive });
|
|
384
|
-
}
|
|
385
287
|
try {
|
|
386
288
|
// Setup devmode and gravity (if using public URL)
|
|
387
289
|
const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
|
|
@@ -491,61 +393,113 @@ export const command = createCommand({
|
|
|
491
393
|
bottomSpacer: false,
|
|
492
394
|
centerTitle: false,
|
|
493
395
|
});
|
|
494
|
-
//
|
|
495
|
-
//
|
|
396
|
+
// Detect user route mount paths for Vite proxy configuration
|
|
397
|
+
// This is a quick AST scan of app.ts — runs before Vite starts
|
|
398
|
+
let routePaths = ['/api']; // Default fallback
|
|
399
|
+
try {
|
|
400
|
+
const { detectExplicitRouter } = await import('../build/app-router-detector');
|
|
401
|
+
const detection = await detectExplicitRouter(rootDir, logger);
|
|
402
|
+
if (detection.detected && detection.mounts.length > 0) {
|
|
403
|
+
routePaths = detection.mounts.map((m) => m.path);
|
|
404
|
+
logger.debug('Detected route mount paths: %s', routePaths.join(', '));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
logger.debug('Route detection failed, using default /api: %s', err);
|
|
409
|
+
}
|
|
410
|
+
// Pick internal ports (neither is user-facing — the front-door proxy is)
|
|
411
|
+
const bunBackendPort = opts.port + 1;
|
|
412
|
+
const viteInternalPort = opts.port + 2;
|
|
413
|
+
// No-bundle dev mode guard: ensure stale bundled app artifact cannot be executed.
|
|
414
|
+
// We keep other .agentuity artifacts (metadata/workbench files) intact.
|
|
415
|
+
try {
|
|
416
|
+
const staleBundlePath = join(rootDir, '.agentuity', 'app.js');
|
|
417
|
+
if (existsSync(staleBundlePath)) {
|
|
418
|
+
await Bun.file(staleBundlePath).delete();
|
|
419
|
+
logger.debug('Removed stale dev bundle artifact: %s', staleBundlePath);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
logger.debug('Failed to remove stale dev bundle artifact: %s', err);
|
|
424
|
+
}
|
|
425
|
+
// Debug trace: locate unexpected legacy credential warnings.
|
|
426
|
+
// Enable with AGENTUITY_TRACE_CREDENTIAL_WARNINGS=true.
|
|
427
|
+
if (process.env.AGENTUITY_TRACE_CREDENTIAL_WARNINGS === 'true') {
|
|
428
|
+
const originalConsoleError = console.error.bind(console);
|
|
429
|
+
console.error = (...args) => {
|
|
430
|
+
try {
|
|
431
|
+
const first = typeof args[0] === 'string' ? args[0] : '';
|
|
432
|
+
if (first.includes('No credentials found for this AI provider')) {
|
|
433
|
+
const stack = new Error('Credential warning trace').stack;
|
|
434
|
+
originalConsoleError('[TRACE] Credential warning origin stack:');
|
|
435
|
+
if (stack)
|
|
436
|
+
originalConsoleError(stack);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// ignore tracing errors
|
|
441
|
+
}
|
|
442
|
+
originalConsoleError(...args);
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
// Start Vite dev server on an internal port.
|
|
446
|
+
// The user-facing port is handled by the front-door TCP proxy (ws-proxy)
|
|
447
|
+
// which routes WS upgrades to Bun and everything else to Vite.
|
|
496
448
|
let viteServer = null;
|
|
497
449
|
let vitePort;
|
|
498
450
|
try {
|
|
499
|
-
logger.debug('Starting Vite
|
|
451
|
+
logger.debug('Starting Vite dev server (internal port %d)...', viteInternalPort);
|
|
500
452
|
const viteResult = await startViteAssetServer({
|
|
501
453
|
rootDir,
|
|
502
454
|
logger,
|
|
503
455
|
workbenchPath: workbench.config?.route,
|
|
456
|
+
port: viteInternalPort,
|
|
457
|
+
backendPort: bunBackendPort,
|
|
458
|
+
routePaths,
|
|
504
459
|
});
|
|
505
460
|
viteServer = viteResult.server;
|
|
506
461
|
vitePort = viteResult.port;
|
|
507
462
|
// Update dev lock with actual Vite port
|
|
508
463
|
await devLock.updatePorts({ vite: vitePort });
|
|
509
|
-
logger.debug(`Vite
|
|
464
|
+
logger.debug(`Vite dev server running on port ${vitePort} (internal, proxying backend on port ${bunBackendPort})`);
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
tui.error(`Failed to start Vite dev server: ${error}`);
|
|
468
|
+
await devLock.release();
|
|
469
|
+
originalExit(1);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// Start the front-door TCP proxy on the user-facing port.
|
|
473
|
+
// Routes WebSocket upgrades (for /api/*, /_agentuity/*) directly to Bun
|
|
474
|
+
// and everything else (HTTP, HMR WebSocket) to Vite.
|
|
475
|
+
// This works around Bun's broken node:http upgrade socket implementation.
|
|
476
|
+
let frontDoorServer = null;
|
|
477
|
+
try {
|
|
478
|
+
const { startWsProxy } = await import('../build/vite/ws-proxy');
|
|
479
|
+
frontDoorServer = await startWsProxy({
|
|
480
|
+
port: opts.port,
|
|
481
|
+
vitePort,
|
|
482
|
+
backendPort: bunBackendPort,
|
|
483
|
+
routePaths,
|
|
484
|
+
logger,
|
|
485
|
+
});
|
|
486
|
+
logger.debug(`Front-door proxy on port ${opts.port} (Vite:${vitePort}, Bun:${bunBackendPort})`);
|
|
510
487
|
}
|
|
511
488
|
catch (error) {
|
|
512
|
-
tui.error(`Failed to start
|
|
489
|
+
tui.error(`Failed to start front-door proxy: ${error}`);
|
|
513
490
|
await devLock.release();
|
|
514
491
|
originalExit(1);
|
|
515
492
|
return;
|
|
516
493
|
}
|
|
517
|
-
//
|
|
518
|
-
// Vite stays running and handles frontend changes via HMR
|
|
519
|
-
let shouldRestart = false;
|
|
494
|
+
// --- State for long-running processes ---
|
|
520
495
|
let gravityProcess = null;
|
|
521
496
|
let gravityHeartbeatInterval = null;
|
|
522
|
-
let stdinListenerRegistered = false;
|
|
523
|
-
const restartServer = () => {
|
|
524
|
-
shouldRestart = true;
|
|
525
|
-
};
|
|
526
|
-
const showWelcome = () => {
|
|
527
|
-
logger.info('DevMode ready 🚀');
|
|
528
|
-
};
|
|
529
|
-
// Create file watcher for backend hot reload
|
|
530
|
-
const fileWatcher = createFileWatcher({
|
|
531
|
-
rootDir,
|
|
532
|
-
logger,
|
|
533
|
-
onRestart: restartServer,
|
|
534
|
-
});
|
|
535
|
-
// Start file watcher (will be paused during builds)
|
|
536
|
-
fileWatcher.start();
|
|
537
|
-
// Track if cleanup is in progress to avoid duplicate cleanup
|
|
538
|
-
let cleaningUp = false;
|
|
539
|
-
// Track if shutdown was requested (SIGINT/SIGTERM) to break the main loop
|
|
540
|
-
let shutdownRequested = false;
|
|
541
|
-
// Store stdin data handler reference for cleanup
|
|
497
|
+
let stdinListenerRegistered = false;
|
|
542
498
|
let stdinDataHandler = null;
|
|
499
|
+
let shutdownRequested = false;
|
|
500
|
+
let cleaningUp = false;
|
|
543
501
|
/**
|
|
544
502
|
* Centralized cleanup function for all resources.
|
|
545
|
-
* Called on restart, shutdown, and fatal errors.
|
|
546
|
-
* @param exitAfter - If true, exit the process after cleanup
|
|
547
|
-
* @param exitCode - Exit code to use if exitAfter is true
|
|
548
|
-
* @param silent - If true, don't show "Shutting down" message
|
|
549
503
|
*/
|
|
550
504
|
const cleanup = async (exitAfter = false, exitCode = 0, silent = false) => {
|
|
551
505
|
if (cleaningUp)
|
|
@@ -554,83 +508,53 @@ export const command = createCommand({
|
|
|
554
508
|
if (!silent) {
|
|
555
509
|
tui.info('Shutting down...');
|
|
556
510
|
}
|
|
557
|
-
// Stop
|
|
558
|
-
try {
|
|
559
|
-
fileWatcher.stop();
|
|
560
|
-
}
|
|
561
|
-
catch (err) {
|
|
562
|
-
logger.debug('Error stopping file watcher: %s', err);
|
|
563
|
-
}
|
|
564
|
-
// Stop Bun server
|
|
511
|
+
// Stop front-door proxy
|
|
565
512
|
try {
|
|
566
|
-
|
|
513
|
+
frontDoorServer?.close();
|
|
567
514
|
}
|
|
568
515
|
catch (err) {
|
|
569
|
-
logger.debug('Error stopping
|
|
516
|
+
logger.debug('Error stopping front-door proxy: %s', err);
|
|
570
517
|
}
|
|
518
|
+
// Kill Bun subprocess
|
|
519
|
+
killBunSubprocess(logger);
|
|
571
520
|
// Stop gravity heartbeat interval
|
|
572
521
|
if (gravityHeartbeatInterval) {
|
|
573
522
|
clearInterval(gravityHeartbeatInterval);
|
|
574
523
|
gravityHeartbeatInterval = null;
|
|
575
524
|
}
|
|
576
|
-
// Kill gravity client
|
|
525
|
+
// Kill gravity client
|
|
577
526
|
if (gravityProcess) {
|
|
578
|
-
logger.debug('Killing gravity process...');
|
|
579
527
|
try {
|
|
580
528
|
gravityProcess.kill('SIGTERM');
|
|
581
|
-
// Give it a moment to gracefully shutdown
|
|
582
529
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
583
530
|
if (gravityProcess.exitCode === null) {
|
|
584
531
|
gravityProcess.kill('SIGKILL');
|
|
585
532
|
}
|
|
586
|
-
logger.debug('Gravity process killed');
|
|
587
533
|
}
|
|
588
534
|
catch (err) {
|
|
589
|
-
logger.debug('Error killing gravity
|
|
535
|
+
logger.debug('Error killing gravity: %s', err);
|
|
590
536
|
}
|
|
591
537
|
finally {
|
|
592
538
|
gravityProcess = null;
|
|
593
539
|
}
|
|
594
540
|
}
|
|
595
|
-
// Close Vite
|
|
541
|
+
// Close Vite
|
|
596
542
|
if (viteServer) {
|
|
597
|
-
logger.debug('Closing Vite server...');
|
|
598
543
|
try {
|
|
599
|
-
// Use Promise.race with timeout to prevent hanging
|
|
600
544
|
const closePromise = viteServer.close();
|
|
601
|
-
const timeoutPromise = new Promise((resolve) =>
|
|
602
|
-
setTimeout(() => {
|
|
603
|
-
logger.debug('Vite server close timed out, continuing...');
|
|
604
|
-
resolve();
|
|
605
|
-
}, 2000);
|
|
606
|
-
});
|
|
545
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 2000));
|
|
607
546
|
await Promise.race([closePromise, timeoutPromise]);
|
|
608
|
-
logger.debug('Vite server closed');
|
|
609
547
|
}
|
|
610
548
|
catch (err) {
|
|
611
|
-
logger.debug('Error closing Vite
|
|
549
|
+
logger.debug('Error closing Vite: %s', err);
|
|
612
550
|
}
|
|
613
551
|
finally {
|
|
614
552
|
viteServer = null;
|
|
615
553
|
}
|
|
616
554
|
}
|
|
617
|
-
|
|
618
|
-
logger.debug('Releasing dev lock...');
|
|
619
|
-
try {
|
|
620
|
-
await devLock.release();
|
|
621
|
-
logger.debug('Dev lock released');
|
|
622
|
-
}
|
|
623
|
-
catch (err) {
|
|
624
|
-
logger.debug('Error releasing dev lock: %s', err);
|
|
625
|
-
}
|
|
555
|
+
await devLock.release();
|
|
626
556
|
await killLingeringGravityProcesses(logger);
|
|
627
|
-
|
|
628
|
-
if (!exitAfter) {
|
|
629
|
-
cleaningUp = false;
|
|
630
|
-
}
|
|
631
|
-
else {
|
|
632
|
-
// Clean up stdin keyboard handler right before exiting
|
|
633
|
-
// This must happen AFTER all async cleanup to keep event loop alive
|
|
557
|
+
if (exitAfter) {
|
|
634
558
|
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
635
559
|
try {
|
|
636
560
|
if (stdinDataHandler) {
|
|
@@ -642,588 +566,313 @@ export const command = createCommand({
|
|
|
642
566
|
process.stdin.unref();
|
|
643
567
|
}
|
|
644
568
|
catch {
|
|
645
|
-
// Ignore
|
|
569
|
+
// Ignore
|
|
646
570
|
}
|
|
647
571
|
}
|
|
648
|
-
logger.debug('Exiting with code %d', exitCode);
|
|
649
572
|
originalExit(exitCode);
|
|
650
573
|
}
|
|
651
574
|
};
|
|
652
|
-
|
|
653
|
-
* Cleanup for restart: stops Bun server and Gravity, keeps Vite running
|
|
654
|
-
*/
|
|
655
|
-
const cleanupForRestart = async () => {
|
|
656
|
-
logger.debug('Cleaning up for restart...');
|
|
657
|
-
// Stop Bun server
|
|
658
|
-
try {
|
|
659
|
-
await stopBunServer(opts.port, logger);
|
|
660
|
-
}
|
|
661
|
-
catch (err) {
|
|
662
|
-
logger.debug('Error stopping Bun server for restart: %s', err);
|
|
663
|
-
}
|
|
664
|
-
// Stop gravity heartbeat interval
|
|
665
|
-
if (gravityHeartbeatInterval) {
|
|
666
|
-
clearInterval(gravityHeartbeatInterval);
|
|
667
|
-
gravityHeartbeatInterval = null;
|
|
668
|
-
}
|
|
669
|
-
// Kill gravity client
|
|
670
|
-
if (gravityProcess) {
|
|
671
|
-
try {
|
|
672
|
-
gravityProcess.kill('SIGTERM');
|
|
673
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
674
|
-
if (gravityProcess.exitCode === null) {
|
|
675
|
-
gravityProcess.kill('SIGKILL');
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
catch (err) {
|
|
679
|
-
logger.debug('Error killing gravity process for restart: %s', err);
|
|
680
|
-
}
|
|
681
|
-
finally {
|
|
682
|
-
gravityProcess = null;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
// SIGINT/SIGTERM: coordinate shutdown between bundle and dev resources
|
|
687
|
-
let signalHandlersRegistered = false;
|
|
575
|
+
// Signal handlers
|
|
688
576
|
let exitingFromSignal = false;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
});
|
|
709
|
-
process.on('SIGTERM', () => {
|
|
710
|
-
safeExit(0, 'SIGTERM');
|
|
711
|
-
});
|
|
712
|
-
// Handle SIGHUP (terminal closed) - same as SIGINT
|
|
713
|
-
process.on('SIGHUP', () => {
|
|
714
|
-
safeExit(0, 'SIGHUP');
|
|
715
|
-
});
|
|
716
|
-
// Handle uncaught exceptions - clean up and exit rather than limping on
|
|
717
|
-
process.on('uncaughtException', (err) => {
|
|
718
|
-
tui.error(`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
719
|
-
void safeExit(1, 'uncaughtException');
|
|
720
|
-
});
|
|
721
|
-
// Handle unhandled rejections - log but don't exit (usually recoverable)
|
|
722
|
-
process.on('unhandledRejection', (reason) => {
|
|
723
|
-
logger.warn('Unhandled promise rejection: %s', reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
// Ensure resources are always cleaned up on exit (synchronous fallback)
|
|
577
|
+
const safeExit = (code, reason) => {
|
|
578
|
+
if (exitingFromSignal)
|
|
579
|
+
return;
|
|
580
|
+
exitingFromSignal = true;
|
|
581
|
+
if (reason)
|
|
582
|
+
logger.debug('DevMode terminating (%d): %s', code, reason);
|
|
583
|
+
shutdownRequested = true;
|
|
584
|
+
cleanup(true, code).catch(() => originalExit(1));
|
|
585
|
+
};
|
|
586
|
+
process.on('SIGINT', () => safeExit(0, 'SIGINT'));
|
|
587
|
+
process.on('SIGTERM', () => safeExit(0, 'SIGTERM'));
|
|
588
|
+
process.on('SIGHUP', () => safeExit(0, 'SIGHUP'));
|
|
589
|
+
process.on('uncaughtException', (err) => {
|
|
590
|
+
tui.error(`Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
|
591
|
+
void safeExit(1, 'uncaughtException');
|
|
592
|
+
});
|
|
593
|
+
process.on('unhandledRejection', (reason) => {
|
|
594
|
+
logger.warn('Unhandled promise rejection: %s', reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
595
|
+
});
|
|
727
596
|
process.on('exit', () => {
|
|
728
|
-
|
|
729
|
-
if (stdinListenerRegistered && process.stdin.isTTY) {
|
|
730
|
-
try {
|
|
731
|
-
if (stdinDataHandler) {
|
|
732
|
-
process.stdin.removeListener('data', stdinDataHandler);
|
|
733
|
-
}
|
|
734
|
-
process.stdin.setRawMode(false);
|
|
735
|
-
process.stdin.pause();
|
|
736
|
-
process.stdin.unref();
|
|
737
|
-
}
|
|
738
|
-
catch {
|
|
739
|
-
// Ignore errors during exit cleanup
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
// Kill gravity client with SIGKILL for immediate termination
|
|
743
|
-
if (gravityProcess && gravityProcess.exitCode === null) {
|
|
597
|
+
if (gravityProcess?.exitCode === null) {
|
|
744
598
|
try {
|
|
745
599
|
gravityProcess.kill('SIGKILL');
|
|
746
600
|
}
|
|
747
601
|
catch {
|
|
748
|
-
// Ignore
|
|
602
|
+
// Ignore
|
|
749
603
|
}
|
|
750
604
|
}
|
|
751
|
-
// Close Vite server synchronously if possible
|
|
752
605
|
if (viteServer) {
|
|
753
606
|
try {
|
|
754
607
|
viteServer.close();
|
|
755
608
|
}
|
|
756
609
|
catch {
|
|
757
|
-
// Ignore
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
// Stop Bun server synchronously (best effort)
|
|
761
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
762
|
-
const server = globalThis.__AGENTUITY_SERVER__;
|
|
763
|
-
if (server?.stop) {
|
|
764
|
-
try {
|
|
765
|
-
server.stop(true);
|
|
766
|
-
}
|
|
767
|
-
catch {
|
|
768
|
-
// Ignore errors during exit cleanup
|
|
610
|
+
// Ignore
|
|
769
611
|
}
|
|
770
612
|
}
|
|
771
|
-
|
|
613
|
+
killBunSubprocess(logger);
|
|
772
614
|
releaseLockSync(rootDir);
|
|
773
615
|
});
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
//
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (!opts.noTypecheck) {
|
|
789
|
-
const typeResult = await typecheck(rootDir);
|
|
790
|
-
if (!typeResult.success) {
|
|
791
|
-
typeCheckErrors = typeResult.output;
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// Step 1: Generate workbench files if enabled (must be done before entry generation)
|
|
796
|
-
if (workbenchConfigData.enabled) {
|
|
797
|
-
logger.debug('Workbench enabled, generating source files before bundle...');
|
|
798
|
-
const { generateWorkbenchFiles } = await import('../build/vite/workbench-generator');
|
|
799
|
-
await generateWorkbenchFiles(rootDir, project?.projectId ?? '', workbenchConfigData, logger);
|
|
800
|
-
}
|
|
801
|
-
// Step 2: Discover agents and routes in parallel
|
|
802
|
-
const srcDir = join(rootDir, 'src');
|
|
803
|
-
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
804
|
-
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
805
|
-
const { generateAgentRegistry, generateRouteRegistry } = await import('../build/vite/registry-generator');
|
|
806
|
-
const [agentMetadata, { routes, routeInfoList }] = await Promise.all([
|
|
807
|
-
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
808
|
-
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
809
|
-
]);
|
|
810
|
-
// Step 2.5: Compute a hash of discovery results to skip codegen when unchanged
|
|
811
|
-
// This avoids rewriting identical files on every restart
|
|
812
|
-
const discoveryFingerprint = Bun.hash(JSON.stringify({
|
|
813
|
-
agents: agentMetadata.map((a) => a.id + a.filename),
|
|
814
|
-
routes: routeInfoList.map((r) => r.method + r.path + r.filename),
|
|
815
|
-
})).toString(36);
|
|
816
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
817
|
-
const prevFingerprint = globalThis
|
|
818
|
-
.__AGENTUITY_DISCOVERY_FINGERPRINT__;
|
|
819
|
-
const discoveryChanged = discoveryFingerprint !== prevFingerprint;
|
|
820
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
821
|
-
globalThis.__AGENTUITY_DISCOVERY_FINGERPRINT__ = discoveryFingerprint;
|
|
822
|
-
if (discoveryChanged) {
|
|
823
|
-
// Generate agent and route registries for type augmentation
|
|
824
|
-
// (TypeScript needs these files to exist for proper type inference)
|
|
825
|
-
generateAgentRegistry(srcDir, agentMetadata);
|
|
826
|
-
generateRouteRegistry(srcDir, routeInfoList);
|
|
827
|
-
logger.debug('Agent and route registries generated for dev mode');
|
|
828
|
-
// Step 3: Generate entry file with workbench and analytics config
|
|
829
|
-
// Pass pre-discovered routes to avoid redundant route discovery
|
|
830
|
-
const { generateEntryFile } = await import('../build/entry-generator');
|
|
831
|
-
await generateEntryFile({
|
|
832
|
-
rootDir,
|
|
833
|
-
projectId: project?.projectId ?? '',
|
|
834
|
-
deploymentId,
|
|
835
|
-
logger,
|
|
836
|
-
mode: 'dev',
|
|
837
|
-
workbench: workbenchConfigData.enabled ? workbenchConfigData : undefined,
|
|
838
|
-
analytics: agentuityConfig?.analytics,
|
|
839
|
-
noBundle: opts.experimentalNoBundle,
|
|
840
|
-
preDiscoveredRoutes: routeInfoList,
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
logger.debug('Discovery unchanged (fingerprint: %s), skipping codegen', discoveryFingerprint);
|
|
845
|
-
}
|
|
846
|
-
// Step 4: Bundle the app with LLM patches (skip in --experimental-no-bundle mode)
|
|
847
|
-
if (!opts.experimentalNoBundle) {
|
|
848
|
-
// This produces .agentuity/app.js with AI Gateway routing patches applied
|
|
849
|
-
// Must re-bundle even if discovery unchanged (user code may have changed)
|
|
850
|
-
const { installExternalsAndBuild } = await import('../build/vite/server-bundler');
|
|
851
|
-
await installExternalsAndBuild({
|
|
852
|
-
rootDir,
|
|
853
|
-
dev: true,
|
|
854
|
-
logger,
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
logger.debug('Skipping Bun.build (--experimental-no-bundle mode)');
|
|
859
|
-
}
|
|
860
|
-
// Generate metadata file (needed for eval ID lookup at runtime)
|
|
861
|
-
// Reuse agentMetadata and routes from Step 2
|
|
862
|
-
const { generateMetadata, writeMetadataFile } = await import('../build/vite/metadata-generator');
|
|
863
|
-
const promises = [];
|
|
864
|
-
// Generate/update prompt files (non-blocking)
|
|
865
|
-
promises.push(import('../build/vite/prompt-generator')
|
|
866
|
-
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
867
|
-
.catch((err) => logger.warn('Failed to generate prompt files: %s', err.message)));
|
|
868
|
-
const metadata = await generateMetadata({
|
|
869
|
-
rootDir,
|
|
870
|
-
projectId: project?.projectId ?? '',
|
|
871
|
-
orgId: project?.orgId ?? '',
|
|
872
|
-
deploymentId,
|
|
873
|
-
agents: agentMetadata,
|
|
874
|
-
routes,
|
|
875
|
-
dev: true,
|
|
876
|
-
logger,
|
|
877
|
-
});
|
|
878
|
-
writeMetadataFile(rootDir, metadata, true, logger);
|
|
879
|
-
// Sync metadata with backend (creates agents and evals in the database)
|
|
880
|
-
if (syncService && project?.projectId) {
|
|
881
|
-
promises.push(syncService.sync(metadata, previousMetadata, project.projectId, deploymentId));
|
|
882
|
-
previousMetadata = metadata;
|
|
883
|
-
}
|
|
884
|
-
await Promise.all(promises);
|
|
885
|
-
},
|
|
886
|
-
clearOnSuccess: true,
|
|
887
|
-
});
|
|
888
|
-
if (typeCheckErrors) {
|
|
889
|
-
console.log('');
|
|
890
|
-
console.log(typeCheckErrors);
|
|
891
|
-
console.log('');
|
|
892
|
-
fileWatcher.resume();
|
|
893
|
-
// wait for a file change or shutdown to trigger a recompile
|
|
894
|
-
while (!shutdownRequested && !shouldRestart) {
|
|
895
|
-
await tui.spinner({
|
|
896
|
-
message: 'Waiting for changes...',
|
|
897
|
-
clearOnSuccess: true,
|
|
898
|
-
callback: async () => {
|
|
899
|
-
// Check more frequently so CTRL+C is responsive
|
|
900
|
-
for (let i = 0; i < 10; i++) {
|
|
901
|
-
if (shutdownRequested || shouldRestart) {
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
await Bun.sleep(100);
|
|
905
|
-
}
|
|
906
|
-
},
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (shutdownRequested) {
|
|
910
|
-
return;
|
|
616
|
+
// ================================================================
|
|
617
|
+
// Step 1: Prepare dev server (once)
|
|
618
|
+
// ================================================================
|
|
619
|
+
await tui.spinner({
|
|
620
|
+
message: 'Preparing dev server',
|
|
621
|
+
callback: async () => {
|
|
622
|
+
// Typecheck (skip with --no-typecheck)
|
|
623
|
+
if (!opts.noTypecheck) {
|
|
624
|
+
const typeResult = await typecheck(rootDir);
|
|
625
|
+
if (!typeResult.success) {
|
|
626
|
+
// Non-fatal in dev: log errors and continue
|
|
627
|
+
console.log('');
|
|
628
|
+
console.log(typeResult.output);
|
|
629
|
+
console.log('');
|
|
911
630
|
}
|
|
912
|
-
// Re-enter the main loop to re-typecheck and rebuild
|
|
913
|
-
// Without this, the code falls through and tries to start the server
|
|
914
|
-
// with the old/stale bundle instead of rebuilding first
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
catch (error) {
|
|
919
|
-
tui.error(`Failed to build dev bundle: ${error}`);
|
|
920
|
-
tui.warning('Waiting for file changes to retry...');
|
|
921
|
-
// Resume watcher to detect changes for retry
|
|
922
|
-
fileWatcher.resume();
|
|
923
|
-
// Wait for next restart trigger or shutdown
|
|
924
|
-
await new Promise((resolve) => {
|
|
925
|
-
const checkRestart = setInterval(() => {
|
|
926
|
-
if (shouldRestart || shutdownRequested) {
|
|
927
|
-
clearInterval(checkRestart);
|
|
928
|
-
resolve();
|
|
929
|
-
}
|
|
930
|
-
}, 100);
|
|
931
|
-
});
|
|
932
|
-
if (shutdownRequested) {
|
|
933
|
-
break;
|
|
934
631
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
process.env.AGENTUITY_REGION = project.region;
|
|
957
|
-
}
|
|
958
|
-
process.env.PORT = String(opts.port);
|
|
959
|
-
process.env.AGENTUITY_PORT = process.env.PORT;
|
|
960
|
-
process.env.AGENTUITY_BASE_URL =
|
|
961
|
-
process.env.AGENTUITY_BASE_URL || `http://localhost:${opts.port}`;
|
|
962
|
-
if (opts.resume) {
|
|
963
|
-
process.env.AGENTUITY_CODER_RESUME_SESSION = opts.resume;
|
|
964
|
-
}
|
|
965
|
-
if (project) {
|
|
966
|
-
// Set environment variables for LLM provider patches
|
|
967
|
-
// These must be set so the bundled patches can route LLM calls through AI Gateway
|
|
968
|
-
const serviceUrls = getServiceUrls(project.region);
|
|
969
|
-
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
970
|
-
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
971
|
-
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
972
|
-
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
973
|
-
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
974
|
-
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
975
|
-
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
976
|
-
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
977
|
-
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
978
|
-
}
|
|
979
|
-
if (devmode?.hostname) {
|
|
980
|
-
process.env.AGENTUITY_DEVMODE_URL = `https://${devmode.hostname}`;
|
|
981
|
-
}
|
|
982
|
-
else {
|
|
983
|
-
process.env.AGENTUITY_DEVMODE_URL = `http://localhost:${opts.port}`;
|
|
984
|
-
}
|
|
985
|
-
// Set Vite port for asset proxying in bundled app
|
|
986
|
-
process.env.VITE_PORT = String(vitePort);
|
|
987
|
-
logger.debug('Set VITE_PORT=%s for asset proxying', process.env.VITE_PORT);
|
|
988
|
-
// Start Bun dev server (Vite already running, just start backend)
|
|
989
|
-
await startBunDevServer({
|
|
632
|
+
// Generate workbench files if enabled
|
|
633
|
+
if (workbenchConfigData.enabled) {
|
|
634
|
+
const { generateWorkbenchFiles } = await import('../build/vite/workbench-generator');
|
|
635
|
+
await generateWorkbenchFiles(rootDir, project?.projectId ?? '', workbenchConfigData, logger);
|
|
636
|
+
}
|
|
637
|
+
// Discover agents and routes in parallel
|
|
638
|
+
const srcDir = join(rootDir, 'src');
|
|
639
|
+
const { discoverAgents } = await import('../build/vite/agent-discovery');
|
|
640
|
+
const { discoverRoutes } = await import('../build/vite/route-discovery');
|
|
641
|
+
const [agentMetadata, { routes }] = await Promise.all([
|
|
642
|
+
discoverAgents(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
643
|
+
discoverRoutes(srcDir, project?.projectId ?? '', deploymentId, logger),
|
|
644
|
+
]);
|
|
645
|
+
// Generate metadata file
|
|
646
|
+
const { generateMetadata, writeMetadataFile } = await import('../build/vite/metadata-generator');
|
|
647
|
+
const promises = [];
|
|
648
|
+
// Generate prompt files (non-blocking)
|
|
649
|
+
promises.push(import('../build/vite/prompt-generator')
|
|
650
|
+
.then(({ generatePromptFiles }) => generatePromptFiles(srcDir, logger))
|
|
651
|
+
.catch((err) => logger.warn('Failed to generate prompt files: %s', err.message)));
|
|
652
|
+
const metadata = await generateMetadata({
|
|
990
653
|
rootDir,
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
orgId: project?.orgId,
|
|
654
|
+
projectId: project?.projectId ?? '',
|
|
655
|
+
orgId: project?.orgId ?? '',
|
|
994
656
|
deploymentId,
|
|
657
|
+
agents: agentMetadata,
|
|
658
|
+
routes,
|
|
659
|
+
dev: true,
|
|
995
660
|
logger,
|
|
996
|
-
vitePort, // Pass port of already-running Vite server
|
|
997
|
-
inspect: opts.inspect,
|
|
998
|
-
inspectWait: opts.inspectWait,
|
|
999
|
-
inspectBrk: opts.inspectBrk,
|
|
1000
|
-
noBundle: opts.experimentalNoBundle,
|
|
1001
661
|
});
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
662
|
+
writeMetadataFile(rootDir, metadata, true, logger);
|
|
663
|
+
// Sync metadata with backend
|
|
664
|
+
if (syncService && project?.projectId) {
|
|
665
|
+
promises.push(syncService.sync(metadata, previousMetadata, project.projectId, deploymentId));
|
|
666
|
+
previousMetadata = metadata;
|
|
667
|
+
}
|
|
668
|
+
await Promise.all(promises);
|
|
669
|
+
},
|
|
670
|
+
clearOnSuccess: true,
|
|
671
|
+
});
|
|
672
|
+
// ================================================================
|
|
673
|
+
// Step 2: Set environment variables
|
|
674
|
+
// ================================================================
|
|
675
|
+
if (!process.env.AGENTUITY_SDK_KEY) {
|
|
676
|
+
const sdkKey = await loadProjectSDKKey(logger, rootDir);
|
|
677
|
+
if (sdkKey) {
|
|
678
|
+
process.env.AGENTUITY_SDK_KEY = sdkKey;
|
|
679
|
+
}
|
|
680
|
+
else if (project) {
|
|
681
|
+
tui.warning('AGENTUITY_SDK_KEY not found in .env file. Numerous features will be unavailable.');
|
|
682
|
+
tui.bullet(`Run "${getCommand('cloud env pull')}" to sync your SDK key, or add AGENTUITY_SDK_KEY to your .env file.`);
|
|
1006
683
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
684
|
+
}
|
|
685
|
+
process.env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
686
|
+
process.env.AGENTUITY_RUNTIME = 'yes';
|
|
687
|
+
process.env.AGENTUITY_ENV = 'development';
|
|
688
|
+
process.env.NODE_ENV = 'development';
|
|
689
|
+
process.env.AGENTUITY_PROJECT_DIR = rootDir;
|
|
690
|
+
if (project?.region) {
|
|
691
|
+
process.env.AGENTUITY_REGION = project.region;
|
|
692
|
+
}
|
|
693
|
+
process.env.PORT = String(bunBackendPort);
|
|
694
|
+
process.env.AGENTUITY_PORT = String(bunBackendPort);
|
|
695
|
+
process.env.AGENTUITY_BASE_URL =
|
|
696
|
+
process.env.AGENTUITY_BASE_URL || `http://localhost:${vitePort}`;
|
|
697
|
+
process.env.AGENTUITY_NO_BUNDLE = 'true';
|
|
698
|
+
if (opts.resume) {
|
|
699
|
+
process.env.AGENTUITY_CODER_RESUME_SESSION = opts.resume;
|
|
700
|
+
}
|
|
701
|
+
if (project) {
|
|
702
|
+
const serviceUrls = getServiceUrls(project.region);
|
|
703
|
+
process.env.AGENTUITY_TRANSPORT_URL = serviceUrls.catalyst;
|
|
704
|
+
process.env.AGENTUITY_CATALYST_URL = serviceUrls.catalyst;
|
|
705
|
+
process.env.AGENTUITY_VECTOR_URL = serviceUrls.vector;
|
|
706
|
+
process.env.AGENTUITY_KEYVALUE_URL = serviceUrls.keyvalue;
|
|
707
|
+
process.env.AGENTUITY_SANDBOX_URL = serviceUrls.sandbox;
|
|
708
|
+
process.env.AGENTUITY_STREAM_URL = serviceUrls.stream;
|
|
709
|
+
process.env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
710
|
+
process.env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
711
|
+
process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID = deploymentId;
|
|
712
|
+
}
|
|
713
|
+
if (devmode?.hostname) {
|
|
714
|
+
process.env.AGENTUITY_DEVMODE_URL = `https://${devmode.hostname}`;
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
process.env.AGENTUITY_DEVMODE_URL = `http://localhost:${vitePort}`;
|
|
718
|
+
}
|
|
719
|
+
// ================================================================
|
|
720
|
+
// Step 3: Start Bun backend with --hot (handles its own HMR)
|
|
721
|
+
// ================================================================
|
|
722
|
+
await startBunDevServer({
|
|
723
|
+
rootDir,
|
|
724
|
+
port: bunBackendPort,
|
|
725
|
+
logger,
|
|
726
|
+
vitePort,
|
|
727
|
+
inspect: opts.inspect,
|
|
728
|
+
inspectWait: opts.inspectWait,
|
|
729
|
+
inspectBrk: opts.inspectBrk,
|
|
730
|
+
});
|
|
731
|
+
// ================================================================
|
|
732
|
+
// Step 4: Start gravity tunnel (if public URL enabled)
|
|
733
|
+
// ================================================================
|
|
734
|
+
if (gravityBin && gravityURL && devmode && project) {
|
|
735
|
+
const privateKeyPEM = devmode.privateKey ?? savedPrivateKey;
|
|
736
|
+
if (!privateKeyPEM) {
|
|
737
|
+
throw new Error('No private key available for gravity connection. Please re-run to generate a new key.');
|
|
738
|
+
}
|
|
739
|
+
gravityProcess = Bun.spawn([
|
|
740
|
+
gravityBin,
|
|
741
|
+
'--endpoint-id',
|
|
742
|
+
devmode.id,
|
|
743
|
+
'--port',
|
|
744
|
+
vitePort.toString(),
|
|
745
|
+
'--url',
|
|
746
|
+
gravityURL,
|
|
747
|
+
'--log-level',
|
|
748
|
+
process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
|
|
749
|
+
'--org-id',
|
|
750
|
+
project.orgId,
|
|
751
|
+
'--project-id',
|
|
752
|
+
project.projectId,
|
|
753
|
+
'--private-key',
|
|
754
|
+
Buffer.from(privateKeyPEM).toString('base64'),
|
|
755
|
+
'--health-check',
|
|
756
|
+
], {
|
|
757
|
+
cwd: rootDir,
|
|
758
|
+
stdout: 'pipe',
|
|
759
|
+
stderr: 'pipe',
|
|
760
|
+
detached: false,
|
|
761
|
+
});
|
|
762
|
+
const gravityPid = gravityProcess.pid;
|
|
763
|
+
if (gravityPid) {
|
|
764
|
+
await devLock.registerChild({
|
|
765
|
+
pid: gravityPid,
|
|
766
|
+
type: 'gravity',
|
|
767
|
+
description: 'Gravity public URL tunnel',
|
|
1022
768
|
});
|
|
1023
|
-
if (shutdownRequested) {
|
|
1024
|
-
break;
|
|
1025
|
-
}
|
|
1026
|
-
continue;
|
|
1027
|
-
}
|
|
1028
|
-
// Exit early if shutdown was requested
|
|
1029
|
-
if (shutdownRequested) {
|
|
1030
|
-
break;
|
|
1031
769
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
'--org-id',
|
|
1051
|
-
project.orgId,
|
|
1052
|
-
'--project-id',
|
|
1053
|
-
project.projectId,
|
|
1054
|
-
'--private-key',
|
|
1055
|
-
Buffer.from(privateKeyPEM).toString('base64'),
|
|
1056
|
-
'--health-check',
|
|
1057
|
-
], {
|
|
1058
|
-
cwd: rootDir,
|
|
1059
|
-
stdout: 'pipe',
|
|
1060
|
-
stderr: 'pipe',
|
|
1061
|
-
detached: false, // Ensure gravity dies with parent process
|
|
1062
|
-
});
|
|
1063
|
-
// Register gravity process in dev lock for cleanup tracking
|
|
1064
|
-
const gravityPid = gravityProcess.pid;
|
|
1065
|
-
if (gravityPid) {
|
|
1066
|
-
await devLock.registerChild({
|
|
1067
|
-
pid: gravityPid,
|
|
1068
|
-
type: 'gravity',
|
|
1069
|
-
description: 'Gravity public URL tunnel',
|
|
1070
|
-
});
|
|
1071
|
-
}
|
|
1072
|
-
// Log gravity output and detect heartbeat port
|
|
1073
|
-
(async () => {
|
|
1074
|
-
try {
|
|
1075
|
-
if (gravityProcess?.stdout) {
|
|
1076
|
-
for await (const chunk of gravityProcess.stdout) {
|
|
1077
|
-
const text = new TextDecoder().decode(chunk);
|
|
1078
|
-
const trimmed = text.trim();
|
|
1079
|
-
// Check for heartbeat port announcement
|
|
1080
|
-
const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
|
|
1081
|
-
if (match?.[1]) {
|
|
1082
|
-
const heartbeatPort = parseInt(match[1], 10);
|
|
1083
|
-
logger.debug('Gravity heartbeat port detected: %d', heartbeatPort);
|
|
1084
|
-
// Start sending heartbeats every 5 seconds
|
|
1085
|
-
if (!gravityHeartbeatInterval) {
|
|
1086
|
-
const sendHeartbeat = async () => {
|
|
1087
|
-
try {
|
|
1088
|
-
await fetch(`http://127.0.0.1:${heartbeatPort}/heartbeat`, {
|
|
1089
|
-
method: 'POST',
|
|
1090
|
-
signal: AbortSignal.timeout(2000),
|
|
1091
|
-
});
|
|
1092
|
-
logger.trace('Gravity heartbeat sent');
|
|
1093
|
-
}
|
|
1094
|
-
catch (err) {
|
|
1095
|
-
logger.trace('Gravity heartbeat failed: %s', err);
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
// Send initial heartbeat immediately
|
|
1099
|
-
sendHeartbeat();
|
|
1100
|
-
// Then send every 5 seconds
|
|
1101
|
-
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
770
|
+
// Log gravity output and detect heartbeat port
|
|
771
|
+
(async () => {
|
|
772
|
+
try {
|
|
773
|
+
if (gravityProcess?.stdout) {
|
|
774
|
+
for await (const chunk of gravityProcess.stdout) {
|
|
775
|
+
const text = new TextDecoder().decode(chunk);
|
|
776
|
+
const trimmed = text.trim();
|
|
777
|
+
const match = trimmed.match(/^HEARTBEAT_PORT=(\d+)$/m);
|
|
778
|
+
if (match?.[1]) {
|
|
779
|
+
const heartbeatPort = parseInt(match[1], 10);
|
|
780
|
+
logger.debug('Gravity heartbeat port: %d', heartbeatPort);
|
|
781
|
+
if (!gravityHeartbeatInterval) {
|
|
782
|
+
const sendHeartbeat = async () => {
|
|
783
|
+
try {
|
|
784
|
+
await fetch(`http://127.0.0.1:${heartbeatPort}/heartbeat`, {
|
|
785
|
+
method: 'POST',
|
|
786
|
+
signal: AbortSignal.timeout(2000),
|
|
787
|
+
});
|
|
1102
788
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
789
|
+
catch {
|
|
790
|
+
// Ignore heartbeat failures
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
sendHeartbeat();
|
|
794
|
+
gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
|
|
1107
795
|
}
|
|
1108
796
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
logger.error('Error reading gravity stdout: %s', err);
|
|
1112
|
-
}
|
|
1113
|
-
})();
|
|
1114
|
-
(async () => {
|
|
1115
|
-
try {
|
|
1116
|
-
if (gravityProcess?.stderr) {
|
|
1117
|
-
for await (const chunk of gravityProcess.stderr) {
|
|
1118
|
-
const text = new TextDecoder().decode(chunk);
|
|
1119
|
-
logger.warn('[gravity] %s', text.trim());
|
|
1120
|
-
}
|
|
797
|
+
else if (trimmed) {
|
|
798
|
+
logger.debug('[gravity] %s', trimmed);
|
|
1121
799
|
}
|
|
1122
800
|
}
|
|
1123
|
-
|
|
1124
|
-
logger.error('Error reading gravity stderr: %s', err);
|
|
1125
|
-
}
|
|
1126
|
-
})();
|
|
1127
|
-
logger.debug('Gravity client started');
|
|
801
|
+
}
|
|
1128
802
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
process.stdin.isTTY &&
|
|
1132
|
-
process.stdout.isTTY &&
|
|
1133
|
-
!stdinListenerRegistered) {
|
|
1134
|
-
stdinListenerRegistered = true;
|
|
1135
|
-
process.stdin.setRawMode(true);
|
|
1136
|
-
process.stdin.resume();
|
|
1137
|
-
process.stdin.setEncoding('utf8');
|
|
1138
|
-
const showHelp = () => {
|
|
1139
|
-
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
1140
|
-
console.log(tui.muted(' h') + ' - show this help');
|
|
1141
|
-
console.log(tui.muted(' c') + ' - clear console');
|
|
1142
|
-
console.log(tui.muted(' q') + ' - quit\n');
|
|
1143
|
-
};
|
|
1144
|
-
// Store handler reference for cleanup
|
|
1145
|
-
stdinDataHandler = (data) => {
|
|
1146
|
-
const key = data.toString();
|
|
1147
|
-
// Handle Ctrl+C or q - trigger graceful shutdown
|
|
1148
|
-
if (key === '\u0003' || key === 'q') {
|
|
1149
|
-
// Remove stdin listener immediately to prevent re-entrancy
|
|
1150
|
-
if (stdinDataHandler) {
|
|
1151
|
-
process.stdin.removeListener('data', stdinDataHandler);
|
|
1152
|
-
stdinDataHandler = null;
|
|
1153
|
-
}
|
|
1154
|
-
// Set shutdown flag and trigger cleanup directly
|
|
1155
|
-
shutdownRequested = true;
|
|
1156
|
-
cleanup(true, 0).catch((err) => {
|
|
1157
|
-
logger.debug('Cleanup error: %s', err);
|
|
1158
|
-
originalExit(1);
|
|
1159
|
-
});
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
switch (key) {
|
|
1163
|
-
case 'h':
|
|
1164
|
-
showHelp();
|
|
1165
|
-
break;
|
|
1166
|
-
case 'c':
|
|
1167
|
-
console.clear();
|
|
1168
|
-
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
1169
|
-
padding: 2,
|
|
1170
|
-
topSpacer: false,
|
|
1171
|
-
bottomSpacer: false,
|
|
1172
|
-
centerTitle: false,
|
|
1173
|
-
});
|
|
1174
|
-
break;
|
|
1175
|
-
default:
|
|
1176
|
-
process.stdout.write(data);
|
|
1177
|
-
break;
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
process.stdin.on('data', stdinDataHandler);
|
|
803
|
+
catch (err) {
|
|
804
|
+
logger.error('Error reading gravity stdout: %s', err);
|
|
1181
805
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
if (shouldRestart || shutdownRequested) {
|
|
1189
|
-
clearInterval(checkRestart);
|
|
1190
|
-
resolve();
|
|
806
|
+
})();
|
|
807
|
+
(async () => {
|
|
808
|
+
try {
|
|
809
|
+
if (gravityProcess?.stderr) {
|
|
810
|
+
for await (const chunk of gravityProcess.stderr) {
|
|
811
|
+
logger.warn('[gravity] %s', new TextDecoder().decode(chunk).trim());
|
|
1191
812
|
}
|
|
1192
|
-
}
|
|
1193
|
-
});
|
|
1194
|
-
// Exit loop if shutdown was requested
|
|
1195
|
-
if (shutdownRequested) {
|
|
1196
|
-
break;
|
|
813
|
+
}
|
|
1197
814
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
// Clean up Bun server and Gravity (Vite stays running)
|
|
1201
|
-
await cleanupForRestart();
|
|
1202
|
-
// Brief pause before restart
|
|
1203
|
-
await Bun.sleep(500);
|
|
1204
|
-
}
|
|
1205
|
-
catch (error) {
|
|
1206
|
-
tui.error(`Error during server operation: ${error}`);
|
|
1207
|
-
tui.warning('Waiting for file changes to retry...');
|
|
1208
|
-
// Cleanup on error (Vite stays running)
|
|
1209
|
-
await cleanupForRestart();
|
|
1210
|
-
// Exit if shutdown was requested during error handling
|
|
1211
|
-
if (shutdownRequested) {
|
|
1212
|
-
break;
|
|
815
|
+
catch (err) {
|
|
816
|
+
logger.error('Error reading gravity stderr: %s', err);
|
|
1213
817
|
}
|
|
1214
|
-
|
|
1215
|
-
fileWatcher.resume();
|
|
1216
|
-
// Wait for next restart trigger or shutdown
|
|
1217
|
-
await new Promise((resolve) => {
|
|
1218
|
-
const checkRestart = setInterval(() => {
|
|
1219
|
-
if (shouldRestart || shutdownRequested) {
|
|
1220
|
-
clearInterval(checkRestart);
|
|
1221
|
-
resolve();
|
|
1222
|
-
}
|
|
1223
|
-
}, 100);
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
818
|
+
})();
|
|
1226
819
|
}
|
|
820
|
+
// ================================================================
|
|
821
|
+
// Step 5: Keyboard shortcuts + wait for shutdown
|
|
822
|
+
// ================================================================
|
|
823
|
+
if (interactive && process.stdin.isTTY && process.stdout.isTTY) {
|
|
824
|
+
stdinListenerRegistered = true;
|
|
825
|
+
process.stdin.setRawMode(true);
|
|
826
|
+
process.stdin.resume();
|
|
827
|
+
process.stdin.setEncoding('utf8');
|
|
828
|
+
const showHelp = () => {
|
|
829
|
+
console.log('\n' + tui.bold('Keyboard Shortcuts:'));
|
|
830
|
+
console.log(tui.muted(' h') + ' - show this help');
|
|
831
|
+
console.log(tui.muted(' c') + ' - clear console');
|
|
832
|
+
console.log(tui.muted(' q') + ' - quit\n');
|
|
833
|
+
};
|
|
834
|
+
stdinDataHandler = (data) => {
|
|
835
|
+
const key = data.toString();
|
|
836
|
+
if (key === '\u0003' || key === 'q') {
|
|
837
|
+
if (stdinDataHandler) {
|
|
838
|
+
process.stdin.removeListener('data', stdinDataHandler);
|
|
839
|
+
stdinDataHandler = null;
|
|
840
|
+
}
|
|
841
|
+
shutdownRequested = true;
|
|
842
|
+
cleanup(true, 0).catch(() => originalExit(1));
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
switch (key) {
|
|
846
|
+
case 'h':
|
|
847
|
+
showHelp();
|
|
848
|
+
break;
|
|
849
|
+
case 'c':
|
|
850
|
+
console.clear();
|
|
851
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
852
|
+
padding: 2,
|
|
853
|
+
topSpacer: false,
|
|
854
|
+
bottomSpacer: false,
|
|
855
|
+
centerTitle: false,
|
|
856
|
+
});
|
|
857
|
+
break;
|
|
858
|
+
default:
|
|
859
|
+
process.stdout.write(data);
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
process.stdin.on('data', stdinDataHandler);
|
|
864
|
+
}
|
|
865
|
+
logger.info('DevMode ready 🚀');
|
|
866
|
+
// Block until shutdown — bun --hot handles backend HMR,
|
|
867
|
+
// Vite handles frontend HMR. Nothing to restart.
|
|
868
|
+
await new Promise((resolve) => {
|
|
869
|
+
const check = setInterval(() => {
|
|
870
|
+
if (shutdownRequested) {
|
|
871
|
+
clearInterval(check);
|
|
872
|
+
resolve();
|
|
873
|
+
}
|
|
874
|
+
}, 200);
|
|
875
|
+
});
|
|
1227
876
|
}
|
|
1228
877
|
finally {
|
|
1229
878
|
/* brute force clean up */
|