@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,697 @@
1
+ // Hatchway CLI - Built with Rollup
2
+ import chalk from 'chalk';
3
+ import { homedir, userInfo } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { r as reactExports, u as useInput, j as jsxRuntimeExports, B as Box, c as colors, T as Text, a as useStdout, b as render, R as React } from './theme-NAQBkisB.js';
6
+ import { l as logger } from './logger-6V5cBxba.js';
7
+ import { c as configManager } from './config-manager-DST6RbP8.js';
8
+ import { startRunner } from '../index.js';
9
+ import 'node:stream';
10
+ import 'node:process';
11
+ import 'node:events';
12
+ import { u as useApp } from './use-app-Ct3w2jLI.js';
13
+ import { u as useBuildState, a as useLogEntries, B as BuildPanel } from './useBuildState-Dy7pRR8Z.js';
14
+ import { g as getVersionInfo, B as Banner } from './Banner-DL1Fpz_g.js';
15
+ import { T as TextInput } from './index-DCC6HGdr.js';
16
+ import { g as getLogBuffer, i as initRunnerLogger, s as setFileLoggerTuiMode } from './runner-logger-instance-Dj_JMznn.js';
17
+ import { o as openBrowser, h as hasStoredToken, g as getStoredToken, p as performOAuthLogin, s as storeToken } from './cli-auth-B4Do-N8Y.js';
18
+ import 'node:fs';
19
+ import 'assert';
20
+ import 'events';
21
+ import 'module';
22
+ import 'node:buffer';
23
+ import 'conf';
24
+ import '@sentry/node';
25
+ import '@anthropic-ai/claude-agent-sdk';
26
+ import '@openai/codex-sdk';
27
+ import 'dotenv';
28
+ import 'node:url';
29
+ import 'fs/promises';
30
+ import 'path';
31
+ import 'ws';
32
+ import 'drizzle-orm/node-postgres';
33
+ import 'pg';
34
+ import 'drizzle-orm/pg-core';
35
+ import 'drizzle-orm';
36
+ import 'crypto';
37
+ import 'drizzle-orm/node-postgres/migrator';
38
+ import 'zod';
39
+ import 'node:child_process';
40
+ import 'node:crypto';
41
+ import 'express';
42
+ import 'node:net';
43
+ import 'node:fs/promises';
44
+ import 'simple-git';
45
+ import 'os';
46
+ import 'fs';
47
+ import './manager-DjVI7erc.js';
48
+ import 'http';
49
+ import 'http-proxy';
50
+ import 'zlib';
51
+ import 'node:http';
52
+
53
+ function LogPanel({ entries, isVerbose, width, height, isFocused }) {
54
+ const [scrollOffset, setScrollOffset] = reactExports.useState(0);
55
+ const [autoScroll, setAutoScroll] = reactExports.useState(true);
56
+ // Filter entries based on verbose mode
57
+ const visibleEntries = isVerbose
58
+ ? entries
59
+ : entries.filter(e => !e.verbose);
60
+ // Available height for log lines (subtract 2 for border, 1 for header)
61
+ const visibleLines = Math.max(1, height - 3);
62
+ // Auto-scroll when new entries arrive
63
+ reactExports.useEffect(() => {
64
+ if (autoScroll && visibleEntries.length > 0) {
65
+ const maxScroll = Math.max(0, visibleEntries.length - visibleLines);
66
+ setScrollOffset(maxScroll);
67
+ }
68
+ }, [visibleEntries.length, autoScroll, visibleLines]);
69
+ // Handle keyboard navigation
70
+ useInput((input, key) => {
71
+ if (!isFocused)
72
+ return;
73
+ if (key.upArrow) {
74
+ setAutoScroll(false);
75
+ setScrollOffset(prev => Math.max(0, prev - 1));
76
+ }
77
+ else if (key.downArrow) {
78
+ const maxScroll = Math.max(0, visibleEntries.length - visibleLines);
79
+ setScrollOffset(prev => Math.min(maxScroll, prev + 1));
80
+ // Re-enable auto-scroll if we're at the bottom
81
+ if (scrollOffset >= maxScroll - 1) {
82
+ setAutoScroll(true);
83
+ }
84
+ }
85
+ else if (key.pageUp) {
86
+ setAutoScroll(false);
87
+ setScrollOffset(prev => Math.max(0, prev - visibleLines));
88
+ }
89
+ else if (key.pageDown) {
90
+ const maxScroll = Math.max(0, visibleEntries.length - visibleLines);
91
+ setScrollOffset(prev => Math.min(maxScroll, prev + visibleLines));
92
+ }
93
+ });
94
+ // Get visible slice of entries
95
+ const displayedEntries = visibleEntries.slice(scrollOffset, scrollOffset + visibleLines);
96
+ return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: isFocused ? colors.cyan : colors.darkGray, paddingX: 1, children: [jsxRuntimeExports.jsxs(Box, { justifyContent: "space-between", marginBottom: 0, children: [jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "LOGS" }), jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["[verbose: ", isVerbose ? 'on' : 'off', "]"] })] }), jsxRuntimeExports.jsx(Box, { flexDirection: "column", flexGrow: 1, children: displayedEntries.length === 0 ? (jsxRuntimeExports.jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "Waiting for logs..." }) })) : (displayedEntries.map((entry, index) => (jsxRuntimeExports.jsx(LogEntryRow, { entry: entry, maxWidth: width - 4 }, entry.id)))) }), visibleEntries.length > visibleLines && (jsxRuntimeExports.jsx(Box, { justifyContent: "flex-end", children: jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: [scrollOffset + 1, "-", Math.min(scrollOffset + visibleLines, visibleEntries.length), "/", visibleEntries.length, autoScroll ? ' (auto)' : ''] }) }))] }));
97
+ }
98
+ // Individual log entry row
99
+ function LogEntryRow({ entry, maxWidth }) {
100
+ const time = new Date(entry.timestamp).toLocaleTimeString('en-US', {
101
+ hour12: false,
102
+ hour: '2-digit',
103
+ minute: '2-digit',
104
+ second: '2-digit',
105
+ });
106
+ const levelColors = {
107
+ debug: colors.dimGray,
108
+ info: colors.cyan,
109
+ success: colors.success,
110
+ warn: colors.warning,
111
+ error: colors.error,
112
+ };
113
+ const levelIcons = {
114
+ debug: ' ',
115
+ info: '●',
116
+ success: '✓',
117
+ warn: '⚠',
118
+ error: '✗',
119
+ };
120
+ // Tool calls get special formatting
121
+ if (entry.toolName) {
122
+ const argsText = entry.toolArgs ? ` ${entry.toolArgs}` : '';
123
+ `${entry.toolName}${argsText}`;
124
+ return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: " \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: colors.white, children: entry.toolName }), entry.toolArgs && jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [" ", entry.toolArgs] })] }));
125
+ }
126
+ // Regular log entries
127
+ const color = levelColors[entry.level];
128
+ const icon = levelIcons[entry.level];
129
+ // Truncate message if needed
130
+ const availableWidth = maxWidth - 12; // time + space + icon + space
131
+ const truncatedMessage = entry.message.length > availableWidth
132
+ ? entry.message.substring(0, availableWidth - 3) + '...'
133
+ : entry.message;
134
+ return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsxs(Text, { color: color, children: [" ", icon, " "] }), jsxRuntimeExports.jsx(Text, { color: color, children: truncatedMessage })] }));
135
+ }
136
+
137
+ function StatusBar({ isConnected, isVerbose, buildCount = 0, currentBuildIndex = 0, view, }) {
138
+ // Get version info
139
+ const versionInfo = getVersionInfo();
140
+ // Connection indicator
141
+ const connectionIndicator = (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: isConnected ? colors.success : colors.error, children: isConnected ? '●' : '○' }), jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [' ', isConnected ? 'Connected' : 'Disconnected'] })] }));
142
+ // Build count indicator (only show if multiple builds)
143
+ const buildIndicator = buildCount > 1 ? (jsxRuntimeExports.jsx(Box, { marginRight: 2, children: jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: ["Build ", currentBuildIndex + 1, "/", buildCount] }) })) : null;
144
+ // Shortcuts based on current view
145
+ const shortcuts = view === 'dashboard' ? (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut$1, { letter: "q", label: "quit" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "b", label: "browser" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "v", label: `verbose: ${isVerbose ? 'on' : 'off'}` }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "c", label: "copy" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "t", label: "text view" }), buildCount > 1 && jsxRuntimeExports.jsx(Shortcut$1, { letter: "n/p", label: "switch build" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "\u2191\u2193", label: "scroll" })] })) : view === 'fullLog' ? (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut$1, { letter: "t", label: "dashboard" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "c", label: "copy" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "/", label: "search" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "f", label: "filter" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "\u2191\u2193", label: "scroll" }), jsxRuntimeExports.jsx(Shortcut$1, { letter: "PgUp/Dn", label: "page" })] })) : (jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Shortcut$1, { letter: "Esc", label: "cancel" }) }));
146
+ // Version display
147
+ const versionDisplay = (jsxRuntimeExports.jsx(Box, { marginLeft: 2, children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: versionInfo.display }) }));
148
+ return (jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Box, { children: [connectionIndicator, buildIndicator] }), jsxRuntimeExports.jsxs(Box, { children: [shortcuts, versionDisplay] })] }));
149
+ }
150
+ // Helper component for shortcuts
151
+ function Shortcut$1({ letter, label }) {
152
+ return (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: letter }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "]" }), jsxRuntimeExports.jsx(Text, { color: colors.gray, children: label })] }));
153
+ }
154
+
155
+ function FullLogView({ entries, onBack, onCopy }) {
156
+ const { stdout } = useStdout();
157
+ const terminalHeight = stdout?.rows || 24;
158
+ stdout?.columns || 80;
159
+ const [searchQuery, setSearchQuery] = reactExports.useState('');
160
+ const [isSearching, setIsSearching] = reactExports.useState(false);
161
+ const [filterMode, setFilterMode] = reactExports.useState('all');
162
+ const [scrollOffset, setScrollOffset] = reactExports.useState(0);
163
+ const [searchMode, setSearchMode] = reactExports.useState('highlight');
164
+ // Available height for log lines
165
+ const headerHeight = 3;
166
+ const footerHeight = 2;
167
+ const visibleLines = Math.max(1, terminalHeight - headerHeight - footerHeight);
168
+ // Filter and search entries
169
+ const processedEntries = entries.filter(entry => {
170
+ // Apply filter mode
171
+ if (filterMode === 'errors' && entry.level !== 'error' && entry.level !== 'warn') {
172
+ return false;
173
+ }
174
+ if (filterMode === 'tools' && !entry.toolName) {
175
+ return false;
176
+ }
177
+ if (filterMode !== 'verbose' && entry.verbose) {
178
+ return false;
179
+ }
180
+ // Apply search filter (if in filter mode)
181
+ if (searchQuery && searchMode === 'filter') {
182
+ const query = searchQuery.toLowerCase();
183
+ const messageMatch = entry.message.toLowerCase().includes(query);
184
+ const toolMatch = entry.toolName?.toLowerCase().includes(query);
185
+ const argsMatch = entry.toolArgs?.toLowerCase().includes(query);
186
+ return messageMatch || toolMatch || argsMatch;
187
+ }
188
+ return true;
189
+ });
190
+ // Calculate max scroll
191
+ const maxScroll = Math.max(0, processedEntries.length - visibleLines);
192
+ // Get visible entries
193
+ const visibleEntries = processedEntries.slice(scrollOffset, scrollOffset + visibleLines);
194
+ // Handle keyboard input
195
+ useInput((input, key) => {
196
+ if (isSearching) {
197
+ if (key.escape || key.return) {
198
+ setIsSearching(false);
199
+ }
200
+ return;
201
+ }
202
+ if (input === 't') {
203
+ onBack();
204
+ }
205
+ else if (input === 'c') {
206
+ onCopy();
207
+ }
208
+ else if (input === '/') {
209
+ setIsSearching(true);
210
+ }
211
+ else if (input === 'f') {
212
+ // Cycle through filter modes
213
+ const modes = ['all', 'errors', 'tools', 'verbose'];
214
+ const currentIndex = modes.indexOf(filterMode);
215
+ setFilterMode(modes[(currentIndex + 1) % modes.length]);
216
+ setScrollOffset(0);
217
+ }
218
+ else if (input === 'm') {
219
+ // Toggle search mode
220
+ setSearchMode(prev => prev === 'filter' ? 'highlight' : 'filter');
221
+ }
222
+ else if (key.upArrow) {
223
+ setScrollOffset(prev => Math.max(0, prev - 1));
224
+ }
225
+ else if (key.downArrow) {
226
+ setScrollOffset(prev => Math.min(maxScroll, prev + 1));
227
+ }
228
+ else if (key.pageUp) {
229
+ setScrollOffset(prev => Math.max(0, prev - visibleLines));
230
+ }
231
+ else if (key.pageDown) {
232
+ setScrollOffset(prev => Math.min(maxScroll, prev + visibleLines));
233
+ }
234
+ else if (key.escape) {
235
+ if (searchQuery) {
236
+ setSearchQuery('');
237
+ }
238
+ else {
239
+ onBack();
240
+ }
241
+ }
242
+ });
243
+ // Format time
244
+ const formatTime = (timestamp) => {
245
+ return new Date(timestamp).toLocaleTimeString('en-US', {
246
+ hour12: false,
247
+ hour: '2-digit',
248
+ minute: '2-digit',
249
+ second: '2-digit',
250
+ });
251
+ };
252
+ // Check if text matches search query
253
+ const highlightSearch = (text) => {
254
+ if (!searchQuery || searchMode !== 'highlight') {
255
+ return text;
256
+ }
257
+ const query = searchQuery.toLowerCase();
258
+ const index = text.toLowerCase().indexOf(query);
259
+ if (index === -1) {
260
+ return text;
261
+ }
262
+ return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [text.slice(0, index), jsxRuntimeExports.jsx(Text, { backgroundColor: colors.warning, color: "black", children: text.slice(index, index + searchQuery.length) }), text.slice(index + searchQuery.length)] }));
263
+ };
264
+ return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "LOGS" }), jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "Search: " }), isSearching ? (jsxRuntimeExports.jsx(Box, { borderStyle: "round", borderColor: colors.cyan, paddingX: 1, children: jsxRuntimeExports.jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "type to search..." }) })) : (jsxRuntimeExports.jsxs(Text, { color: searchQuery ? colors.white : colors.dimGray, children: ["[", searchQuery || 'none', "] (", searchMode, ")"] })), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: " [/]" })] })] }), jsxRuntimeExports.jsx(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "single", borderColor: colors.darkGray, borderTop: false, borderBottom: false, paddingX: 1, children: visibleEntries.map((entry, index) => {
265
+ const time = formatTime(entry.timestamp);
266
+ const levelColors = {
267
+ debug: colors.dimGray,
268
+ info: colors.cyan,
269
+ success: colors.success,
270
+ warn: colors.warning,
271
+ error: colors.error,
272
+ };
273
+ const levelIcons = {
274
+ debug: ' ',
275
+ info: '●',
276
+ success: '✓',
277
+ warn: '⚠',
278
+ error: '✗',
279
+ };
280
+ if (entry.toolName) {
281
+ return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: " \uD83D\uDD27 " }), jsxRuntimeExports.jsx(Text, { color: colors.white, children: highlightSearch(entry.toolName) }), entry.toolArgs && (jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [" ", highlightSearch(entry.toolArgs)] }))] }, entry.id));
282
+ }
283
+ return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: time }), jsxRuntimeExports.jsxs(Text, { color: levelColors[entry.level], children: [" ", levelIcons[entry.level], " "] }), jsxRuntimeExports.jsx(Text, { color: levelColors[entry.level], children: highlightSearch(entry.message) })] }, entry.id));
284
+ }) }), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Shortcut, { letter: "t", label: "dashboard" }), jsxRuntimeExports.jsx(Shortcut, { letter: "c", label: "copy" }), jsxRuntimeExports.jsx(Shortcut, { letter: "/", label: "search" }), jsxRuntimeExports.jsx(Shortcut, { letter: "f", label: `filter: ${filterMode}` }), jsxRuntimeExports.jsx(Shortcut, { letter: "m", label: `mode: ${searchMode}` })] }), jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: [scrollOffset + 1, "-", Math.min(scrollOffset + visibleLines, processedEntries.length), "/", processedEntries.length] })] })] }));
285
+ }
286
+ function Shortcut({ letter, label }) {
287
+ return (jsxRuntimeExports.jsxs(Box, { marginRight: 2, children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: letter }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "]" }), jsxRuntimeExports.jsx(Text, { color: colors.gray, children: label })] }));
288
+ }
289
+
290
+ function CopyMenu({ onSelect, onCancel, visibleCount, totalCount }) {
291
+ useInput((input, key) => {
292
+ if (key.escape) {
293
+ onCancel();
294
+ }
295
+ else if (input === '1') {
296
+ onSelect('visible');
297
+ }
298
+ else if (input === '2') {
299
+ onSelect('last50');
300
+ }
301
+ else if (input === '3') {
302
+ onSelect('last100');
303
+ }
304
+ else if (input === '4') {
305
+ onSelect('all');
306
+ }
307
+ else if (input === '5') {
308
+ onSelect('range');
309
+ }
310
+ });
311
+ return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.cyan, paddingX: 2, paddingY: 1, children: [jsxRuntimeExports.jsx(Box, { marginBottom: 1, children: jsxRuntimeExports.jsx(Text, { color: colors.cyan, bold: true, children: "Copy Logs" }) }), jsxRuntimeExports.jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [jsxRuntimeExports.jsx(CopyOption, { number: "1", label: `Copy visible (${visibleCount} lines)` }), jsxRuntimeExports.jsx(CopyOption, { number: "2", label: "Copy last 50 lines" }), jsxRuntimeExports.jsx(CopyOption, { number: "3", label: "Copy last 100 lines" }), jsxRuntimeExports.jsx(CopyOption, { number: "4", label: `Copy all from file (${totalCount} lines)` }), jsxRuntimeExports.jsx(CopyOption, { number: "5", label: "Copy range..." })] }), jsxRuntimeExports.jsx(Box, { children: jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[Esc] Cancel" }) })] }));
312
+ }
313
+ function CopyOption({ number, label }) {
314
+ return (jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "[" }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: number }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: "]" }), jsxRuntimeExports.jsxs(Text, { color: colors.white, children: [" ", label] })] }));
315
+ }
316
+
317
+ function RunnerDashboard({ config, onQuit }) {
318
+ const { exit } = useApp();
319
+ const { stdout } = useStdout();
320
+ const terminalHeight = stdout?.rows || 24;
321
+ const terminalWidth = stdout?.columns || 80;
322
+ // State management
323
+ const [buildState, buildActions] = useBuildState();
324
+ const logEntries = useLogEntries(100);
325
+ const [view, setView] = reactExports.useState('dashboard');
326
+ // Track if we've opened browser on first connection
327
+ const hasOpenedBrowserRef = reactExports.useRef(false);
328
+ // Get the dashboard URL (use apiUrl if provided, otherwise default to hatchway.sh)
329
+ const dashboardUrl = config.apiUrl || 'https://hatchway.sh';
330
+ // Open browser handler
331
+ const handleOpenBrowser = reactExports.useCallback(() => {
332
+ openBrowser(dashboardUrl).catch(() => {
333
+ // Silently fail - user can manually open the URL
334
+ });
335
+ }, [dashboardUrl]);
336
+ // Auto-open browser on first successful connection
337
+ reactExports.useEffect(() => {
338
+ if (buildState.isConnected && !hasOpenedBrowserRef.current) {
339
+ hasOpenedBrowserRef.current = true;
340
+ // Small delay to ensure the TUI is fully rendered before opening browser
341
+ setTimeout(() => {
342
+ handleOpenBrowser();
343
+ }, 500);
344
+ }
345
+ }, [buildState.isConnected, handleOpenBrowser]);
346
+ // Handle keyboard input
347
+ useInput((input, key) => {
348
+ // Global quit handler
349
+ if (input === 'q' && view !== 'copyMenu') {
350
+ if (onQuit) {
351
+ onQuit();
352
+ }
353
+ exit();
354
+ return;
355
+ }
356
+ // Copy menu is modal - handle separately
357
+ if (view === 'copyMenu') {
358
+ return; // CopyMenu handles its own input
359
+ }
360
+ // Dashboard shortcuts
361
+ if (view === 'dashboard') {
362
+ if (input === 'v') {
363
+ buildActions.toggleVerbose();
364
+ }
365
+ else if (input === 'c') {
366
+ setView('copyMenu');
367
+ }
368
+ else if (input === 't') {
369
+ setView('fullLog');
370
+ }
371
+ else if (input === 'n') {
372
+ buildActions.nextBuild();
373
+ }
374
+ else if (input === 'p') {
375
+ buildActions.prevBuild();
376
+ }
377
+ else if (input === 'b') {
378
+ handleOpenBrowser();
379
+ }
380
+ }
381
+ // Full log view shortcuts
382
+ if (view === 'fullLog') {
383
+ if (input === 't') {
384
+ setView('dashboard');
385
+ }
386
+ else if (input === 'c') {
387
+ setView('copyMenu');
388
+ }
389
+ }
390
+ });
391
+ // Handle copy action
392
+ const handleCopy = reactExports.useCallback(async (option) => {
393
+ try {
394
+ const buffer = getLogBuffer();
395
+ let entriesToCopy = [];
396
+ switch (option) {
397
+ case 'visible':
398
+ entriesToCopy = logEntries.slice(-20); // Approximate visible count
399
+ break;
400
+ case 'last50':
401
+ entriesToCopy = buffer.getRecent(50);
402
+ break;
403
+ case 'last100':
404
+ entriesToCopy = buffer.getRecent(100);
405
+ break;
406
+ case 'all':
407
+ entriesToCopy = buffer.readFromFile();
408
+ break;
409
+ case 'range':
410
+ // TODO: Implement range selection
411
+ entriesToCopy = buffer.getRecent(100);
412
+ break;
413
+ }
414
+ const text = buffer.toText(entriesToCopy);
415
+ // Copy to clipboard using pbcopy on macOS
416
+ const { spawn } = await import('child_process');
417
+ const pbcopy = spawn('pbcopy');
418
+ pbcopy.stdin.write(text);
419
+ pbcopy.stdin.end();
420
+ // TODO: Show success message
421
+ }
422
+ catch (error) {
423
+ console.error('Failed to copy to clipboard:', error);
424
+ }
425
+ setView('dashboard');
426
+ }, [logEntries]);
427
+ // Calculate panel dimensions
428
+ const bannerHeight = 7; // ASCII art banner
429
+ const headerHeight = 3; // Config/status line
430
+ const statusBarHeight = 3;
431
+ const contentHeight = Math.max(1, terminalHeight - bannerHeight - headerHeight - statusBarHeight);
432
+ // 20/80 split
433
+ const buildPanelWidth = Math.floor(terminalWidth * 0.2);
434
+ const logPanelWidth = terminalWidth - buildPanelWidth;
435
+ // Show build panel only when there's an active build
436
+ const showBuildPanel = buildState.currentBuild !== null;
437
+ // Check for available update (set by auto-update check in index.ts)
438
+ const updateAvailable = process.env.HATCHWAY_UPDATE_AVAILABLE;
439
+ return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: terminalHeight, width: terminalWidth, children: [jsxRuntimeExports.jsx(Banner, {}), updateAvailable && (jsxRuntimeExports.jsxs(Box, { justifyContent: "center", paddingY: 0, children: [jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: "\u2B06 Update available: " }), jsxRuntimeExports.jsx(Text, { color: colors.success, children: updateAvailable }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: " \u2014 Run " }), jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: "hatchway upgrade" }), jsxRuntimeExports.jsx(Text, { color: colors.dimGray, children: " to update" })] })), jsxRuntimeExports.jsxs(Box, { borderStyle: "single", borderColor: colors.darkGray, paddingX: 1, justifyContent: "space-between", children: [jsxRuntimeExports.jsxs(Text, { color: colors.dimGray, children: ["Runner: ", jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: config.runnerId }), " \u2022 Server: ", jsxRuntimeExports.jsx(Text, { color: colors.cyan, children: config.serverUrl.replace(/^wss?:\/\//, '') })] }), jsxRuntimeExports.jsxs(Box, { children: [jsxRuntimeExports.jsx(Text, { color: buildState.isConnected ? colors.success : colors.error, children: buildState.isConnected ? '●' : '○' }), jsxRuntimeExports.jsxs(Text, { color: colors.gray, children: [' ', buildState.isConnected ? 'Connected' : 'Disconnected'] })] })] }), view === 'dashboard' && (jsxRuntimeExports.jsxs(Box, { flexGrow: 1, height: contentHeight, children: [showBuildPanel && (jsxRuntimeExports.jsx(BuildPanel, { build: buildState.currentBuild, width: buildPanelWidth, height: contentHeight })), jsxRuntimeExports.jsx(LogPanel, { entries: logEntries, isVerbose: buildState.isVerbose, width: showBuildPanel ? logPanelWidth : terminalWidth, height: contentHeight, isFocused: true })] })), view === 'fullLog' && (jsxRuntimeExports.jsx(FullLogView, { entries: logEntries, onBack: () => setView('dashboard'), onCopy: () => setView('copyMenu') })), view === 'copyMenu' && (jsxRuntimeExports.jsx(Box, { position: "absolute", flexDirection: "column", justifyContent: "center", alignItems: "center", width: terminalWidth, height: terminalHeight, children: jsxRuntimeExports.jsx(CopyMenu, { onSelect: handleCopy, onCancel: () => setView('dashboard'), visibleCount: Math.min(20, logEntries.length), totalCount: getLogBuffer().readFromFile().length }) })), view === 'dashboard' && (jsxRuntimeExports.jsx(StatusBar, { isConnected: buildState.isConnected, isVerbose: buildState.isVerbose, buildCount: buildState.builds.length, currentBuildIndex: buildState.currentBuildIndex, view: view }))] }));
440
+ }
441
+
442
+ // Default public Hatchway instance
443
+ const DEFAULT_URL = 'https://hatchway.sh';
444
+ const DEFAULT_WORKSPACE = join(homedir(), 'hatchway-workspace');
445
+ /**
446
+ * Normalize URL by adding protocol if missing
447
+ * Uses http:// for localhost, https:// for everything else
448
+ */
449
+ function normalizeUrl(url) {
450
+ if (!url)
451
+ return url;
452
+ // If protocol already present, return as-is
453
+ if (url.match(/^https?:\/\//i)) {
454
+ return url;
455
+ }
456
+ // For localhost or 127.0.0.1, use http://
457
+ if (url.match(/^(localhost|127\.0\.0\.1)(:|\/|$)/i)) {
458
+ return `http://${url}`;
459
+ }
460
+ // For everything else, use https://
461
+ return `https://${url}`;
462
+ }
463
+ /**
464
+ * Derive WebSocket URL from a base HTTP/HTTPS URL
465
+ * Converts https://example.com to wss://example.com/ws/runner
466
+ */
467
+ function deriveWsUrl(baseUrl) {
468
+ const normalized = normalizeUrl(baseUrl);
469
+ const wsProtocol = normalized.startsWith('https://') ? 'wss://' : 'ws://';
470
+ const hostPath = normalized.replace(/^https?:\/\//, '');
471
+ // Remove trailing slash if present
472
+ const cleanHostPath = hostPath.replace(/\/$/, '');
473
+ return `${wsProtocol}${cleanHostPath}/ws/runner`;
474
+ }
475
+ /**
476
+ * Get the current system username
477
+ */
478
+ function getSystemUsername() {
479
+ try {
480
+ return userInfo().username;
481
+ }
482
+ catch {
483
+ // Fallback if userInfo() fails
484
+ return process.env.USER || process.env.USERNAME || 'runner';
485
+ }
486
+ }
487
+ /**
488
+ * Check if we should use TUI
489
+ */
490
+ function shouldUseTUI(options) {
491
+ // Explicit flag
492
+ if (options.noTui)
493
+ return false;
494
+ // CI/CD environments
495
+ if (process.env.CI === '1' || process.env.CI === 'true')
496
+ return false;
497
+ // Not a TTY
498
+ if (!process.stdout.isTTY)
499
+ return false;
500
+ // Explicit env var to disable
501
+ if (process.env.NO_TUI === '1')
502
+ return false;
503
+ return true;
504
+ }
505
+ async function runCommand(options) {
506
+ // Set local mode environment variable if requested
507
+ if (options.local) {
508
+ process.env.HATCHWAY_LOCAL_MODE = 'true';
509
+ logger.info(chalk.yellow('Local mode enabled - authentication bypassed'));
510
+ }
511
+ const useTUI = shouldUseTUI(options);
512
+ // Build runner options from CLI flags or smart defaults
513
+ // NOTE: For the `runner` command, we intentionally ignore local config values
514
+ // and default to the public Hatchway instance. This command is specifically
515
+ // for connecting to remote servers, not local development.
516
+ // Users can still override with CLI flags if needed.
517
+ // Resolve API URL: CLI flag > default public instance (ignore config)
518
+ const apiUrl = normalizeUrl(options.url || DEFAULT_URL);
519
+ // Resolve WebSocket URL: CLI broker flag > derive from API URL (ignore config)
520
+ const wsUrl = options.broker || deriveWsUrl(apiUrl);
521
+ // Resolve workspace: CLI flag > config > default ~/hatchway-workspace
522
+ // (workspace from config is fine since it's user's preference for where projects go)
523
+ const config = configManager.get();
524
+ const workspace = options.workspace || config.workspace || DEFAULT_WORKSPACE;
525
+ // Resolve runner ID: CLI flag > system username (ignore config 'local' default)
526
+ const runnerId = options.runnerId || getSystemUsername();
527
+ // Resolve secret: CLI flag > config (required)
528
+ // Only use config secret if it looks like a valid token (starts with sv_)
529
+ // This prevents the default 'dev-secret' from being used in runner mode
530
+ const configSecret = configManager.getSecret();
531
+ const sharedSecret = options.secret || (configSecret?.startsWith('sv_') ? configSecret : undefined);
532
+ const runnerOptions = {
533
+ wsUrl,
534
+ apiUrl,
535
+ sharedSecret,
536
+ runnerId,
537
+ workspace,
538
+ verbose: options.verbose,
539
+ tuiMode: useTUI,
540
+ };
541
+ // Validate required options - secret is required
542
+ // If not provided, try to use stored OAuth token or trigger OAuth flow
543
+ if (!runnerOptions.sharedSecret) {
544
+ // Check if we have a stored OAuth token
545
+ if (hasStoredToken()) {
546
+ const storedToken = getStoredToken();
547
+ if (storedToken) {
548
+ runnerOptions.sharedSecret = storedToken;
549
+ logger.info(`Using stored runner token: ${chalk.cyan(storedToken.substring(0, 12) + '...')}`);
550
+ }
551
+ }
552
+ // If still no secret and not in local mode, trigger OAuth flow
553
+ if (!runnerOptions.sharedSecret && !options.local) {
554
+ logger.info('No runner token found. Starting OAuth authentication...');
555
+ logger.info('');
556
+ const result = await performOAuthLogin({
557
+ apiUrl: runnerOptions.apiUrl,
558
+ silent: false,
559
+ });
560
+ if (result.success && result.token) {
561
+ storeToken(result.token, runnerOptions.apiUrl);
562
+ runnerOptions.sharedSecret = result.token;
563
+ logger.log('');
564
+ logger.success('Authentication successful!');
565
+ logger.info(`Token: ${chalk.cyan(result.token.substring(0, 12) + '...')}`);
566
+ logger.log('');
567
+ }
568
+ else {
569
+ logger.error(result.error || 'Authentication failed');
570
+ logger.info('');
571
+ logger.info('You can also provide a token manually:');
572
+ logger.info(` ${chalk.cyan('hatchway runner --secret <your-secret>')}`);
573
+ logger.info('');
574
+ logger.info('Or login first:');
575
+ logger.info(` ${chalk.cyan('hatchway login')}`);
576
+ process.exit(1);
577
+ }
578
+ }
579
+ // Final check - if still no secret (and not local mode)
580
+ if (!runnerOptions.sharedSecret && !options.local) {
581
+ logger.error('Shared secret is required');
582
+ logger.info('');
583
+ logger.info('Get a runner key from your Hatchway dashboard, or provide via:');
584
+ logger.info(` ${chalk.cyan('hatchway runner --secret <your-secret>')}`);
585
+ logger.info('');
586
+ logger.info('Or login with OAuth:');
587
+ logger.info(` ${chalk.cyan('hatchway login')}`);
588
+ process.exit(1);
589
+ }
590
+ }
591
+ // ========================================
592
+ // PLAIN TEXT MODE (--no-tui)
593
+ // ========================================
594
+ if (!useTUI) {
595
+ // Display startup info
596
+ logger.section('Starting Hatchway Runner');
597
+ logger.info(`Server: ${chalk.cyan(runnerOptions.wsUrl)}`);
598
+ logger.info(`API URL: ${chalk.cyan(runnerOptions.apiUrl)}`);
599
+ logger.info(`Runner ID: ${chalk.cyan(runnerOptions.runnerId)}`);
600
+ logger.info(`Workspace: ${chalk.cyan(runnerOptions.workspace)}`);
601
+ logger.log('');
602
+ if (options.verbose) {
603
+ logger.debug('Verbose logging enabled');
604
+ logger.debug(`Full options: ${JSON.stringify(runnerOptions, null, 2)}`);
605
+ }
606
+ try {
607
+ // Start the runner (runs indefinitely)
608
+ await startRunner(runnerOptions);
609
+ }
610
+ catch (error) {
611
+ logger.error('Failed to start runner:');
612
+ logger.error(error instanceof Error ? error.message : 'Unknown error');
613
+ if (error instanceof Error && error.stack) {
614
+ logger.debug(error.stack);
615
+ }
616
+ process.exit(1);
617
+ }
618
+ return;
619
+ }
620
+ // ========================================
621
+ // TUI MODE (default)
622
+ // ========================================
623
+ // Initialize the logger BEFORE rendering TUI so the TUI can subscribe to events
624
+ // This must happen before startRunner() which would create its own logger
625
+ initRunnerLogger({
626
+ verbose: options.verbose || false,
627
+ tuiMode: true,
628
+ });
629
+ // Enable TUI mode in file-logger to suppress terminal output
630
+ setFileLoggerTuiMode(true);
631
+ // Track runner cleanup function
632
+ let runnerCleanupFn;
633
+ // Clear screen and enter alternate buffer for clean TUI
634
+ process.stdout.write('\x1b[?1049h'); // Enter alternate screen
635
+ process.stdout.write('\x1b[2J\x1b[H'); // Clear and home
636
+ // Ensure stdin is in raw mode for keyboard input
637
+ if (process.stdin.setRawMode) {
638
+ process.stdin.setRawMode(true);
639
+ }
640
+ process.stdin.resume();
641
+ // Handle quit from TUI
642
+ const handleQuit = async () => {
643
+ // Exit alternate screen buffer
644
+ process.stdout.write('\x1b[?1049l');
645
+ console.log('\n' + chalk.yellow('Shutting down runner...'));
646
+ if (runnerCleanupFn) {
647
+ try {
648
+ await runnerCleanupFn();
649
+ console.log(chalk.green('✓') + ' Runner stopped');
650
+ }
651
+ catch (e) {
652
+ console.error(chalk.red('✗') + ' Error stopping runner:', e);
653
+ }
654
+ }
655
+ process.exit(0);
656
+ };
657
+ // Handle SIGINT (Ctrl+C)
658
+ process.on('SIGINT', handleQuit);
659
+ // Render the TUI dashboard
660
+ const { waitUntilExit, clear } = render(React.createElement(RunnerDashboard, {
661
+ config: {
662
+ runnerId,
663
+ serverUrl: wsUrl,
664
+ workspace,
665
+ apiUrl,
666
+ },
667
+ onQuit: handleQuit,
668
+ }), {
669
+ stdin: process.stdin,
670
+ stdout: process.stdout,
671
+ stderr: process.stderr,
672
+ exitOnCtrlC: false, // We handle this ourselves
673
+ patchConsole: false, // We use our own logging
674
+ });
675
+ try {
676
+ // Start the runner and get cleanup function
677
+ runnerCleanupFn = await startRunner(runnerOptions);
678
+ // Wait for TUI to exit (user pressed 'q')
679
+ await waitUntilExit();
680
+ // Clean up
681
+ clear();
682
+ await handleQuit();
683
+ }
684
+ catch (error) {
685
+ clear();
686
+ process.stdout.write('\x1b[?1049l'); // Exit alternate screen
687
+ logger.error('Failed to start runner:');
688
+ logger.error(error instanceof Error ? error.message : 'Unknown error');
689
+ if (error instanceof Error && error.stack) {
690
+ logger.debug(error.stack);
691
+ }
692
+ process.exit(1);
693
+ }
694
+ }
695
+
696
+ export { runCommand };
697
+ //# sourceMappingURL=run-Du6dvTJL.js.map