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