@hatchway/cli 0.50.53

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 (80) hide show
  1. package/README.md +274 -0
  2. package/bin/hatchway.js +31 -0
  3. package/dist/chunks/Banner-DL1Fpz_g.js +115 -0
  4. package/dist/chunks/Banner-DL1Fpz_g.js.map +1 -0
  5. package/dist/chunks/auto-update-Ddo5Ntt7.js +264 -0
  6. package/dist/chunks/auto-update-Ddo5Ntt7.js.map +1 -0
  7. package/dist/chunks/build-V8_D-JHF.js +116 -0
  8. package/dist/chunks/build-V8_D-JHF.js.map +1 -0
  9. package/dist/chunks/cleanup-BNuJNSve.js +141 -0
  10. package/dist/chunks/cleanup-BNuJNSve.js.map +1 -0
  11. package/dist/chunks/cli-auth-B4Do-N8Y.js +340 -0
  12. package/dist/chunks/cli-auth-B4Do-N8Y.js.map +1 -0
  13. package/dist/chunks/cli-error-1drkrXNn.js +140 -0
  14. package/dist/chunks/cli-error-1drkrXNn.js.map +1 -0
  15. package/dist/chunks/config-hFJA7z5y.js +167 -0
  16. package/dist/chunks/config-hFJA7z5y.js.map +1 -0
  17. package/dist/chunks/config-manager-DST6RbP8.js +133 -0
  18. package/dist/chunks/config-manager-DST6RbP8.js.map +1 -0
  19. package/dist/chunks/database-YGb1Lzim.js +68 -0
  20. package/dist/chunks/database-YGb1Lzim.js.map +1 -0
  21. package/dist/chunks/database-setup-U31oEs90.js +253 -0
  22. package/dist/chunks/database-setup-U31oEs90.js.map +1 -0
  23. package/dist/chunks/devtools-CPruVlOo.js +75 -0
  24. package/dist/chunks/devtools-CPruVlOo.js.map +1 -0
  25. package/dist/chunks/index-DCC6HGdr.js +119 -0
  26. package/dist/chunks/index-DCC6HGdr.js.map +1 -0
  27. package/dist/chunks/init-DkXJVFFx.js +472 -0
  28. package/dist/chunks/init-DkXJVFFx.js.map +1 -0
  29. package/dist/chunks/init-tui-D2VOVdeK.js +1131 -0
  30. package/dist/chunks/init-tui-D2VOVdeK.js.map +1 -0
  31. package/dist/chunks/logger-6V5cBxba.js +38 -0
  32. package/dist/chunks/logger-6V5cBxba.js.map +1 -0
  33. package/dist/chunks/login-CA1XWUEM.js +63 -0
  34. package/dist/chunks/login-CA1XWUEM.js.map +1 -0
  35. package/dist/chunks/logout-BC4VFt8f.js +40 -0
  36. package/dist/chunks/logout-BC4VFt8f.js.map +1 -0
  37. package/dist/chunks/main-tui-D8KkJRd_.js +648 -0
  38. package/dist/chunks/main-tui-D8KkJRd_.js.map +1 -0
  39. package/dist/chunks/manager-DjVI7erc.js +1161 -0
  40. package/dist/chunks/manager-DjVI7erc.js.map +1 -0
  41. package/dist/chunks/port-allocator-BENntRMG.js +864 -0
  42. package/dist/chunks/port-allocator-BENntRMG.js.map +1 -0
  43. package/dist/chunks/process-killer-ChXAqhfm.js +87 -0
  44. package/dist/chunks/process-killer-ChXAqhfm.js.map +1 -0
  45. package/dist/chunks/prompts-Beijr8dm.js +128 -0
  46. package/dist/chunks/prompts-Beijr8dm.js.map +1 -0
  47. package/dist/chunks/repo-cloner-UY3L2X7h.js +219 -0
  48. package/dist/chunks/repo-cloner-UY3L2X7h.js.map +1 -0
  49. package/dist/chunks/repo-detector-36VydrlB.js +66 -0
  50. package/dist/chunks/repo-detector-36VydrlB.js.map +1 -0
  51. package/dist/chunks/run-Du6dvTJL.js +697 -0
  52. package/dist/chunks/run-Du6dvTJL.js.map +1 -0
  53. package/dist/chunks/runner-logger-instance-Dj_JMznn.js +899 -0
  54. package/dist/chunks/runner-logger-instance-Dj_JMznn.js.map +1 -0
  55. package/dist/chunks/spinner-DTH0QZQw.js +53 -0
  56. package/dist/chunks/spinner-DTH0QZQw.js.map +1 -0
  57. package/dist/chunks/start-Dkuro1jp.js +1713 -0
  58. package/dist/chunks/start-Dkuro1jp.js.map +1 -0
  59. package/dist/chunks/start-traditional-7wlD2f2H.js +255 -0
  60. package/dist/chunks/start-traditional-7wlD2f2H.js.map +1 -0
  61. package/dist/chunks/status-BU3cFJm1.js +97 -0
  62. package/dist/chunks/status-BU3cFJm1.js.map +1 -0
  63. package/dist/chunks/theme-NAQBkisB.js +40222 -0
  64. package/dist/chunks/theme-NAQBkisB.js.map +1 -0
  65. package/dist/chunks/upgrade-BBpJirEu.js +455 -0
  66. package/dist/chunks/upgrade-BBpJirEu.js.map +1 -0
  67. package/dist/chunks/use-app-Ct3w2jLI.js +10 -0
  68. package/dist/chunks/use-app-Ct3w2jLI.js.map +1 -0
  69. package/dist/chunks/useBuildState-Dy7pRR8Z.js +330 -0
  70. package/dist/chunks/useBuildState-Dy7pRR8Z.js.map +1 -0
  71. package/dist/cli/index.js +712 -0
  72. package/dist/cli/index.js.map +1 -0
  73. package/dist/index.js +13625 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/instrument.js +45 -0
  76. package/dist/instrument.js.map +1 -0
  77. package/dist/templates.json +295 -0
  78. package/package.json +87 -0
  79. package/templates/config.template.json +18 -0
  80. package/templates.json +295 -0
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env node
2
+ // Hatchway CLI - Built with Rollup
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { execFileSync } from 'node:child_process';
7
+ import { Command } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { C as CLIError } from '../chunks/cli-error-1drkrXNn.js';
10
+
11
+ /**
12
+ * ASCII art banner for Hatchway CLI
13
+ */
14
+ // ANSI color codes
15
+ const colors = {
16
+ reset: '\x1b[0m',
17
+ brightPurple: '\x1b[95m',
18
+ cyan: '\x1b[36m'};
19
+ /**
20
+ * Displays the Hatchway banner
21
+ */
22
+ function displayBanner() {
23
+ const banner = `
24
+ ${colors.cyan}██╗ ██╗ █████╗ ████████╗ ██████╗██╗ ██╗${colors.brightPurple}██╗ ██╗ █████╗ ██╗ ██╗${colors.reset}
25
+ ${colors.cyan}██║ ██║██╔══██╗╚══██╔══╝██╔════╝██║ ██║${colors.brightPurple}██║ ██║██╔══██╗╚██╗ ██╔╝${colors.reset}
26
+ ${colors.cyan}███████║███████║ ██║ ██║ ███████║${colors.brightPurple}██║ █╗ ██║███████║ ╚████╔╝ ${colors.reset}
27
+ ${colors.cyan}██╔══██║██╔══██║ ██║ ██║ ██╔══██║${colors.brightPurple}██║███╗██║██╔══██║ ╚██╔╝ ${colors.reset}
28
+ ${colors.cyan}██║ ██║██║ ██║ ██║ ╚██████╗██║ ██║${colors.brightPurple}╚███╔███╔╝██║ ██║ ██║ ${colors.reset}
29
+ ${colors.cyan}╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝${colors.brightPurple} ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ${colors.reset}
30
+ `;
31
+ console.log(banner);
32
+ }
33
+
34
+ /**
35
+ * Centralized error handling and formatting for CLI
36
+ * Displays errors with context, suggestions, and proper formatting
37
+ */
38
+ class CLIErrorHandler {
39
+ constructor(options = {}) {
40
+ this.debug = options.debug ?? process.env.DEBUG === '1';
41
+ this.exitOnError = options.exitOnError ?? true;
42
+ }
43
+ /**
44
+ * Main error handling method
45
+ * Formats and displays error, then exits if fatal
46
+ */
47
+ handle(error) {
48
+ // Convert to CLIError if needed
49
+ const cliError = error instanceof CLIError
50
+ ? error
51
+ : this.convertToCLIError(error);
52
+ // Display the error
53
+ this.display(cliError);
54
+ // Exit if fatal and exitOnError is enabled
55
+ if (cliError.fatal && this.exitOnError) {
56
+ process.exit(cliError.getExitCode());
57
+ }
58
+ }
59
+ /**
60
+ * Display formatted error to console
61
+ */
62
+ display(error) {
63
+ console.error(); // Empty line for spacing
64
+ // Error header
65
+ console.error(chalk.red('✗'), chalk.bold(error.message));
66
+ // Error code (in debug mode)
67
+ if (this.debug) {
68
+ console.error(chalk.dim(` Code: ${error.code}`));
69
+ }
70
+ // Context information
71
+ if (error.context && Object.keys(error.context).length > 0) {
72
+ console.error();
73
+ console.error(chalk.dim(' Details:'));
74
+ Object.entries(error.context).forEach(([key, value]) => {
75
+ console.error(chalk.dim(` ${key}: ${this.formatValue(value)}`));
76
+ });
77
+ }
78
+ // Suggestions
79
+ if (error.suggestions.length > 0) {
80
+ console.error();
81
+ console.error(chalk.yellow(' Try this:'));
82
+ error.suggestions.forEach((suggestion, index) => {
83
+ console.error(chalk.yellow(` ${index + 1}. ${suggestion}`));
84
+ });
85
+ }
86
+ // Documentation link
87
+ if (error.docs) {
88
+ console.error();
89
+ console.error(chalk.dim(' Documentation:'), chalk.cyan(error.docs));
90
+ }
91
+ // Stack trace (debug mode only)
92
+ if (this.debug && error.stack) {
93
+ console.error();
94
+ console.error(chalk.dim(' Stack trace:'));
95
+ console.error(chalk.dim(error.stack));
96
+ }
97
+ // Original error cause (if available)
98
+ if (this.debug && error.cause) {
99
+ console.error();
100
+ console.error(chalk.dim(' Caused by:'));
101
+ console.error(chalk.dim(error.cause.message));
102
+ if (error.cause.stack) {
103
+ console.error(chalk.dim(error.cause.stack));
104
+ }
105
+ }
106
+ console.error(); // Empty line for spacing
107
+ }
108
+ /**
109
+ * Format context values for display
110
+ */
111
+ formatValue(value) {
112
+ if (typeof value === 'string')
113
+ return value;
114
+ if (typeof value === 'number')
115
+ return String(value);
116
+ if (typeof value === 'boolean')
117
+ return String(value);
118
+ if (value === null || value === undefined)
119
+ return 'null';
120
+ if (Array.isArray(value))
121
+ return value.join(', ');
122
+ return JSON.stringify(value);
123
+ }
124
+ /**
125
+ * Convert generic errors to CLIError
126
+ */
127
+ convertToCLIError(error) {
128
+ // Try to infer error code from message
129
+ const code = this.inferErrorCode(error);
130
+ return new CLIError({
131
+ code,
132
+ message: error.message || 'An unexpected error occurred',
133
+ cause: error,
134
+ suggestions: ['Run with --debug flag for more details'],
135
+ });
136
+ }
137
+ /**
138
+ * Infer error code from generic error
139
+ */
140
+ inferErrorCode(error) {
141
+ const message = error.message.toLowerCase();
142
+ if (message.includes('econnrefused') || message.includes('connection refused')) {
143
+ return 'DB_CONNECTION_FAILED';
144
+ }
145
+ if (message.includes('eaddrinuse') || message.includes('address already in use')) {
146
+ return 'PORT_IN_USE';
147
+ }
148
+ if (message.includes('eacces') || message.includes('permission denied')) {
149
+ return 'PERMISSION_DENIED';
150
+ }
151
+ if (message.includes('enoent') || message.includes('no such file')) {
152
+ return 'CONFIG_NOT_FOUND';
153
+ }
154
+ return 'UNKNOWN_ERROR';
155
+ }
156
+ /**
157
+ * Wrap an async function with error handling
158
+ * Usage: await errorHandler.wrap(() => someAsyncFunction(), 'Description')
159
+ */
160
+ async wrap(fn, context) {
161
+ try {
162
+ return await fn();
163
+ }
164
+ catch (error) {
165
+ if (context && error instanceof CLIError) {
166
+ // Create new CLIError with added context (can't modify readonly property)
167
+ const enhancedError = new CLIError({
168
+ code: error.code,
169
+ message: error.message,
170
+ context: { ...error.context, operation: context },
171
+ suggestions: error.suggestions,
172
+ fatal: error.fatal,
173
+ cause: error.cause,
174
+ docs: error.docs,
175
+ });
176
+ this.handle(enhancedError);
177
+ throw enhancedError;
178
+ }
179
+ this.handle(error);
180
+ throw error; // Re-throw for callers that want to handle it
181
+ }
182
+ }
183
+ /**
184
+ * Enable or disable debug mode
185
+ */
186
+ setDebug(debug) {
187
+ this.debug = debug;
188
+ }
189
+ /**
190
+ * Enable or disable exit on error
191
+ */
192
+ setExitOnError(exitOnError) {
193
+ this.exitOnError = exitOnError;
194
+ }
195
+ }
196
+ /**
197
+ * Global error handler instance
198
+ * Can be configured once and used throughout the CLI
199
+ */
200
+ const globalErrorHandler = new CLIErrorHandler();
201
+ /**
202
+ * Setup global error handlers for uncaught errors
203
+ */
204
+ function setupGlobalErrorHandlers() {
205
+ process.on('uncaughtException', (error) => {
206
+ console.error(chalk.red('\n✗ Uncaught exception:'));
207
+ globalErrorHandler.handle(error);
208
+ });
209
+ process.on('unhandledRejection', (reason) => {
210
+ console.error(chalk.red('\n✗ Unhandled promise rejection:'));
211
+ const error = reason instanceof Error ? reason : new Error(String(reason));
212
+ globalErrorHandler.handle(error);
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Graceful shutdown handler for CLI
218
+ * Ensures proper cleanup when user presses Ctrl+C or process is terminated
219
+ */
220
+ class ShutdownHandler {
221
+ constructor(options = {}) {
222
+ this.cleanupFunctions = [];
223
+ this.childProcesses = [];
224
+ this.isShuttingDown = false;
225
+ this.timeout = options.timeout ?? 5000;
226
+ this.verbose = options.verbose !== false;
227
+ }
228
+ /**
229
+ * Register a cleanup function to run on shutdown
230
+ */
231
+ onShutdown(fn) {
232
+ this.cleanupFunctions.push(fn);
233
+ }
234
+ /**
235
+ * Register a child process to kill on shutdown
236
+ */
237
+ registerProcess(process) {
238
+ this.childProcesses.push(process);
239
+ }
240
+ /**
241
+ * Setup signal handlers for graceful shutdown
242
+ */
243
+ setup() {
244
+ // Handle Ctrl+C (SIGINT)
245
+ process.on('SIGINT', () => {
246
+ if (this.verbose) {
247
+ console.log(); // New line after ^C
248
+ console.log(chalk.yellow('⚠'), 'Received interrupt signal (Ctrl+C)');
249
+ }
250
+ this.shutdown('SIGINT');
251
+ });
252
+ // Handle termination (SIGTERM)
253
+ process.on('SIGTERM', () => {
254
+ if (this.verbose) {
255
+ console.log(chalk.yellow('⚠'), 'Received termination signal (SIGTERM)');
256
+ }
257
+ this.shutdown('SIGTERM');
258
+ });
259
+ // Handle process exit
260
+ process.on('exit', (code) => {
261
+ if (this.verbose && code !== 0) {
262
+ console.log(chalk.red('✗'), `Process exiting with code ${code}`);
263
+ }
264
+ });
265
+ }
266
+ /**
267
+ * Execute graceful shutdown
268
+ */
269
+ async shutdown(signal) {
270
+ // Prevent multiple shutdown attempts
271
+ if (this.isShuttingDown) {
272
+ if (this.verbose) {
273
+ console.log(chalk.dim(' Already shutting down...'));
274
+ }
275
+ return;
276
+ }
277
+ this.isShuttingDown = true;
278
+ if (this.verbose) {
279
+ console.log(chalk.cyan('ℹ'), 'Shutting down gracefully...');
280
+ }
281
+ // Set a timeout to force exit if cleanup takes too long
282
+ const forceExitTimeout = setTimeout(() => {
283
+ console.error(chalk.red('✗'), 'Shutdown timeout exceeded, forcing exit');
284
+ process.exit(1);
285
+ }, this.timeout);
286
+ try {
287
+ // 1. Kill child processes first
288
+ if (this.childProcesses.length > 0) {
289
+ if (this.verbose) {
290
+ console.log(chalk.dim(' Stopping child processes...'));
291
+ }
292
+ await this.killChildProcesses();
293
+ }
294
+ // 2. Run cleanup functions
295
+ if (this.cleanupFunctions.length > 0) {
296
+ if (this.verbose) {
297
+ console.log(chalk.dim(' Running cleanup tasks...'));
298
+ }
299
+ await this.runCleanup();
300
+ }
301
+ // 3. Success
302
+ if (this.verbose) {
303
+ console.log(chalk.green('✓'), 'Shutdown complete');
304
+ }
305
+ clearTimeout(forceExitTimeout);
306
+ process.exit(0);
307
+ }
308
+ catch (error) {
309
+ console.error(chalk.red('✗'), 'Error during shutdown:', error);
310
+ clearTimeout(forceExitTimeout);
311
+ process.exit(1);
312
+ }
313
+ }
314
+ /**
315
+ * Kill all registered child processes
316
+ */
317
+ async killChildProcesses() {
318
+ const killPromises = this.childProcesses.map(async (child) => {
319
+ if (!child.killed && child.pid) {
320
+ try {
321
+ // Try graceful SIGTERM first
322
+ child.kill('SIGTERM');
323
+ // Wait up to 2 seconds for graceful shutdown
324
+ await new Promise((resolve) => {
325
+ const timeout = setTimeout(() => {
326
+ // Force kill if still alive
327
+ if (!child.killed) {
328
+ child.kill('SIGKILL');
329
+ }
330
+ resolve();
331
+ }, 2000);
332
+ child.on('exit', () => {
333
+ clearTimeout(timeout);
334
+ resolve();
335
+ });
336
+ });
337
+ }
338
+ catch (error) {
339
+ // Process might already be dead
340
+ if (this.verbose) {
341
+ console.log(chalk.dim(` Could not kill process ${child.pid}`));
342
+ }
343
+ }
344
+ }
345
+ });
346
+ await Promise.all(killPromises);
347
+ }
348
+ /**
349
+ * Run all cleanup functions
350
+ */
351
+ async runCleanup() {
352
+ const cleanupPromises = this.cleanupFunctions.map(async (fn) => {
353
+ try {
354
+ await fn();
355
+ }
356
+ catch (error) {
357
+ if (this.verbose) {
358
+ console.error(chalk.yellow('⚠'), 'Cleanup function failed:', error);
359
+ }
360
+ }
361
+ });
362
+ await Promise.all(cleanupPromises);
363
+ }
364
+ /**
365
+ * Manually trigger shutdown (for testing or programmatic use)
366
+ */
367
+ async triggerShutdown() {
368
+ await this.shutdown('MANUAL');
369
+ }
370
+ /**
371
+ * Remove a child process from tracking (e.g., after it exits naturally)
372
+ */
373
+ unregisterProcess(process) {
374
+ const index = this.childProcesses.indexOf(process);
375
+ if (index > -1) {
376
+ this.childProcesses.splice(index, 1);
377
+ }
378
+ }
379
+ }
380
+ /**
381
+ * Global shutdown handler instance
382
+ */
383
+ new ShutdownHandler();
384
+ /**
385
+ * Helper to setup shutdown handler with common options
386
+ */
387
+ function setupShutdownHandler(options) {
388
+ const handler = new ShutdownHandler(options);
389
+ handler.setup();
390
+ return handler;
391
+ }
392
+
393
+ // EARLY TUI DETECTION: Set SILENT_MODE before any modules are imported
394
+ // This suppresses console output in TUI mode
395
+ // Must be done before imports because file-logger.ts captures console at load time
396
+ {
397
+ const args = process.argv.slice(2);
398
+ const isRunnerTUI = args[0] === 'runner' && !args.includes('--no-tui');
399
+ const isRunTUI = args[0] === 'run';
400
+ const isInitTUI = args[0] === 'init' && (args.includes('-y') || args.includes('--yes') || args.includes('--non-interactive'));
401
+ const isNoArgsTUI = args.length === 0 || (args.length === 1 && args[0] === '--debug');
402
+ if (isRunnerTUI || isRunTUI || isInitTUI || isNoArgsTUI) {
403
+ process.env.SILENT_MODE = '1';
404
+ }
405
+ }
406
+ const __filename$1 = fileURLToPath(import.meta.url);
407
+ const __dirname$1 = dirname(__filename$1);
408
+ // Find the package root by looking for package.json
409
+ // This works regardless of where the bundled code ends up (dist/, dist/cli/, etc.)
410
+ function findPackageRoot(startDir) {
411
+ let dir = startDir;
412
+ for (let i = 0; i < 5; i++) { // Max 5 levels up
413
+ if (existsSync(join(dir, 'package.json'))) {
414
+ return dir;
415
+ }
416
+ const parent = dirname(dir);
417
+ if (parent === dir)
418
+ break; // Reached filesystem root
419
+ dir = parent;
420
+ }
421
+ return startDir; // Fallback to start dir
422
+ }
423
+ const packageRoot = findPackageRoot(__dirname$1);
424
+ // Check if running in development mode (linked via pnpm/npm link)
425
+ // Skip vendor install if we're in the monorepo - dependencies are handled by pnpm
426
+ const isLinkedDevelopment = packageRoot.includes('/hatchway/apps/runner');
427
+ // Only run vendor install for production global installs
428
+ if (!isLinkedDevelopment) {
429
+ // Check if Sentry packages are missing and extract from vendor if needed
430
+ // (agent-core is bundled by tsup, but Sentry packages come from vendor tarballs)
431
+ const nodeModulesDir = dirname(packageRoot); // Go up from package to node_modules/@hatchway
432
+ const sentryNodePath = join(nodeModulesDir, "..", "@sentry", "node");
433
+ if (!existsSync(sentryNodePath)) {
434
+ // Silently initialize vendor packages in background
435
+ try {
436
+ const installScript = join(packageRoot, "scripts/install-vendor.js");
437
+ execFileSync("node", [installScript], {
438
+ cwd: packageRoot,
439
+ stdio: "pipe" // Silent mode - output only shown if VERBOSE=1
440
+ });
441
+ }
442
+ catch (error) {
443
+ console.error("Failed to initialize vendor packages:", error);
444
+ process.exit(1);
445
+ }
446
+ }
447
+ }
448
+ // Get package.json for version info
449
+ const packageJson = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf-8'));
450
+ // Setup global error handlers for uncaught errors
451
+ setupGlobalErrorHandlers();
452
+ // Setup graceful shutdown handlers for Ctrl+C
453
+ const shutdownHandler = setupShutdownHandler({
454
+ timeout: 5000,
455
+ verbose: true,
456
+ });
457
+ // Check if we're running in TUI mode or version mode - skip banner if so
458
+ const args = process.argv.slice(2);
459
+ const isInitWithYes = args[0] === 'init' && (args.includes('-y') || args.includes('--yes') || args.includes('--non-interactive'));
460
+ const isNoArgs = args.length === 0 || (args.length === 1 && args[0] === '--debug');
461
+ const isRunCommand = args[0] === 'run'; // `hatchway run` uses TUI Dashboard
462
+ const isRunnerCommand = args[0] === 'runner' && !args.includes('--no-tui'); // `hatchway runner` uses TUI Dashboard (unless --no-tui)
463
+ const isVersionCommand = args.includes('--version') || args.includes('-V'); // Skip banner for version output
464
+ const isSkipBanner = process.env.HATCHWAY_SKIP_BANNER === '1'; // Skip banner after auto-update restart
465
+ const isTUIMode = isInitWithYes || isNoArgs || isRunCommand || isRunnerCommand;
466
+ const isSilentMode = isTUIMode || isVersionCommand || isSkipBanner;
467
+ // Set SILENT_MODE for TUI/version to suppress all console output from other modules
468
+ // This must be set early, before modules that use console.log are imported
469
+ if (isSilentMode) {
470
+ process.env.SILENT_MODE = '1';
471
+ }
472
+ // Auto-update check - do this BEFORE displaying banner to avoid double banners
473
+ // All modes (TUI and CLI): full auto-update with restart
474
+ // For version mode: skip entirely - just show version
475
+ let willAutoUpdate = false;
476
+ if (!process.env.HATCHWAY_SKIP_UPDATE_CHECK && !isVersionCommand) {
477
+ const { checkAndAutoUpdate } = await import('../chunks/auto-update-Ddo5Ntt7.js');
478
+ try {
479
+ // Show banner first for non-TUI modes
480
+ if (!isSilentMode) {
481
+ displayBanner();
482
+ }
483
+ // Full auto-update for all modes
484
+ const didUpdate = await checkAndAutoUpdate(packageJson.version);
485
+ if (didUpdate) {
486
+ // CLI will be relaunched by auto-update, exit this process
487
+ willAutoUpdate = true;
488
+ process.exit(0);
489
+ }
490
+ }
491
+ catch {
492
+ // Auto-update failed silently, continue with current version
493
+ }
494
+ }
495
+ else if (!isSilentMode) {
496
+ // Update check skipped, show banner for CLI mode (but not version mode)
497
+ displayBanner();
498
+ }
499
+ const program = new Command();
500
+ program
501
+ .name('hatchway')
502
+ .description('Hatchway CLI - AI App Builder')
503
+ .version(packageJson.version)
504
+ .option('--runner', 'Start runner only (connect to remote server)')
505
+ .option('--debug', 'Enable debug mode with verbose error output')
506
+ .hook('preAction', (thisCommand) => {
507
+ // Enable debug mode if --debug flag is present
508
+ const opts = thisCommand.opts();
509
+ if (opts.debug) {
510
+ globalErrorHandler.setDebug(true);
511
+ process.env.DEBUG = '1';
512
+ }
513
+ })
514
+ .action(async (options) => {
515
+ try {
516
+ // Default action when no subcommand is provided
517
+ if (options.runner) {
518
+ // Start runner only (legacy flag)
519
+ const { runCommand } = await import('../chunks/run-Du6dvTJL.js');
520
+ await runCommand({});
521
+ }
522
+ else {
523
+ // Show TUI main menu
524
+ const { mainTUICommand } = await import('../chunks/main-tui-D8KkJRd_.js');
525
+ await mainTUICommand();
526
+ }
527
+ }
528
+ catch (error) {
529
+ globalErrorHandler.handle(error);
530
+ }
531
+ });
532
+ // Import commands
533
+ program
534
+ .command('init')
535
+ .description('Initialize workspace and configuration for local development')
536
+ .option('--workspace <path>', 'Set workspace directory')
537
+ .option('--url <url>', 'Set server URL (default: http://localhost:3000)')
538
+ .option('--secret <secret>', 'Set shared secret')
539
+ .option('--branch <branch>', 'Git branch to clone (default: main)')
540
+ .option('--database [value]', 'Database setup: connection string, or omit to auto-setup Neon in -y mode')
541
+ .option('-y, --yes', 'Accept all defaults (non-interactive mode)')
542
+ .option('--non-interactive', 'Use defaults without prompts (alias for -y)')
543
+ .action(async (options) => {
544
+ try {
545
+ const { initCommand } = await import('../chunks/init-DkXJVFFx.js');
546
+ await initCommand(options);
547
+ }
548
+ catch (error) {
549
+ globalErrorHandler.handle(error);
550
+ }
551
+ });
552
+ program
553
+ .command('run')
554
+ .description('Start the full stack locally (web app + runner)')
555
+ .option('-p, --port <port>', 'Web app port (default: 3000)')
556
+ .option('--dev', 'Use development mode (hot reload, slower startup)')
557
+ .option('--rebuild', 'Rebuild services before starting')
558
+ .option('--no-local', 'Disable local mode (require authentication)')
559
+ .option('--no-tui', 'Disable TUI dashboard, use plain text logs')
560
+ .option('-v, --verbose', 'Enable verbose logging (show debug info)')
561
+ .action(async (options) => {
562
+ try {
563
+ const { startCommand } = await import('../chunks/start-Dkuro1jp.js');
564
+ await startCommand(options);
565
+ }
566
+ catch (error) {
567
+ globalErrorHandler.handle(error);
568
+ }
569
+ });
570
+ program
571
+ .command('build')
572
+ .description('Build all services without starting (useful while app is running)')
573
+ .option('--watch', 'Watch for changes and rebuild automatically')
574
+ .action(async (options) => {
575
+ try {
576
+ const { buildCommand } = await import('../chunks/build-V8_D-JHF.js');
577
+ await buildCommand(options);
578
+ }
579
+ catch (error) {
580
+ globalErrorHandler.handle(error);
581
+ }
582
+ });
583
+ program
584
+ .command('runner')
585
+ .description('Start runner only (connect to Hatchway server)')
586
+ .option('-u, --url <url>', 'Hatchway server URL (default: https://hatchway.sh)')
587
+ .option('-w, --workspace <path>', 'Workspace directory (default: ~/hatchway-workspace)')
588
+ .option('-i, --runner-id <id>', 'Runner identifier (default: system username)')
589
+ .option('-s, --secret <secret>', 'Shared secret for authentication (required)')
590
+ .option('-b, --broker <url>', 'WebSocket URL override (advanced, inferred from --url)')
591
+ .option('-v, --verbose', 'Enable verbose logging')
592
+ .option('-l, --local', 'Enable local mode (bypasses authentication)')
593
+ .option('--no-tui', 'Disable TUI dashboard, use plain text logs')
594
+ .action(async (options) => {
595
+ try {
596
+ const { runCommand } = await import('../chunks/run-Du6dvTJL.js');
597
+ await runCommand(options);
598
+ }
599
+ catch (error) {
600
+ globalErrorHandler.handle(error);
601
+ }
602
+ });
603
+ program
604
+ .command('config <action> [key] [value]')
605
+ .description('Manage configuration (actions: get, set, list, path, validate, reset)')
606
+ .action(async (action, key, value) => {
607
+ try {
608
+ const { configCommand } = await import('../chunks/config-hFJA7z5y.js');
609
+ await configCommand(action, key, value);
610
+ }
611
+ catch (error) {
612
+ globalErrorHandler.handle(error);
613
+ }
614
+ });
615
+ // Alias for config validate
616
+ program
617
+ .command('verify')
618
+ .description('Verify configuration is valid (alias for config validate)')
619
+ .action(async () => {
620
+ try {
621
+ const { configCommand } = await import('../chunks/config-hFJA7z5y.js');
622
+ await configCommand('validate');
623
+ }
624
+ catch (error) {
625
+ globalErrorHandler.handle(error);
626
+ }
627
+ });
628
+ program
629
+ .command('status')
630
+ .description('Show runner status and configuration')
631
+ .action(async () => {
632
+ try {
633
+ const { statusCommand } = await import('../chunks/status-BU3cFJm1.js');
634
+ await statusCommand();
635
+ }
636
+ catch (error) {
637
+ globalErrorHandler.handle(error);
638
+ }
639
+ });
640
+ program
641
+ .command('cleanup')
642
+ .description('Clean up projects and resources')
643
+ .option('--project <slug>', 'Delete specific project')
644
+ .option('--all', 'Clean all projects in workspace')
645
+ .option('--tunnels', 'Close all active tunnels')
646
+ .option('--processes', 'Kill all dev servers')
647
+ .action(async (options) => {
648
+ try {
649
+ const { cleanupCommand } = await import('../chunks/cleanup-BNuJNSve.js');
650
+ await cleanupCommand(options);
651
+ }
652
+ catch (error) {
653
+ globalErrorHandler.handle(error);
654
+ }
655
+ });
656
+ program
657
+ .command('database')
658
+ .alias('db')
659
+ .description('Set up a new database and initialize schema')
660
+ .action(async () => {
661
+ try {
662
+ const { databaseCommand } = await import('../chunks/database-YGb1Lzim.js');
663
+ await databaseCommand();
664
+ }
665
+ catch (error) {
666
+ globalErrorHandler.handle(error);
667
+ }
668
+ });
669
+ program
670
+ .command('upgrade')
671
+ .description('Upgrade to latest version (preserves configuration)')
672
+ .option('--branch <branch>', 'Upgrade to specific branch (default: main)')
673
+ .option('--force', 'Skip safety checks (uncommitted changes)')
674
+ .action(async (options) => {
675
+ try {
676
+ const { upgradeCommand } = await import('../chunks/upgrade-BBpJirEu.js');
677
+ await upgradeCommand(options);
678
+ }
679
+ catch (error) {
680
+ globalErrorHandler.handle(error);
681
+ }
682
+ });
683
+ program
684
+ .command('login')
685
+ .description('Authenticate with Hatchway via OAuth (GitHub/Sentry)')
686
+ .option('-u, --url <url>', 'Hatchway server URL (default: https://hatchway.sh)')
687
+ .option('-f, --force', 'Force re-authentication even if already logged in')
688
+ .action(async (options) => {
689
+ try {
690
+ const { loginCommand } = await import('../chunks/login-CA1XWUEM.js');
691
+ await loginCommand(options);
692
+ }
693
+ catch (error) {
694
+ globalErrorHandler.handle(error);
695
+ }
696
+ });
697
+ program
698
+ .command('logout')
699
+ .description('Clear stored authentication credentials')
700
+ .action(async () => {
701
+ try {
702
+ const { logoutCommand } = await import('../chunks/logout-BC4VFt8f.js');
703
+ await logoutCommand();
704
+ }
705
+ catch (error) {
706
+ globalErrorHandler.handle(error);
707
+ }
708
+ });
709
+ program.parse();
710
+
711
+ export { shutdownHandler };
712
+ //# sourceMappingURL=index.js.map