@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.
Files changed (78) hide show
  1. package/README.md +1053 -0
  2. package/bin/openbuilder.js +31 -0
  3. package/dist/chunks/Banner-D4tqKfzA.js +113 -0
  4. package/dist/chunks/Banner-D4tqKfzA.js.map +1 -0
  5. package/dist/chunks/auto-update-Dj3lWPWO.js +350 -0
  6. package/dist/chunks/auto-update-Dj3lWPWO.js.map +1 -0
  7. package/dist/chunks/build-D0qYqIq0.js +116 -0
  8. package/dist/chunks/build-D0qYqIq0.js.map +1 -0
  9. package/dist/chunks/cleanup-qVTsA3tk.js +141 -0
  10. package/dist/chunks/cleanup-qVTsA3tk.js.map +1 -0
  11. package/dist/chunks/cli-error-BjQwvWtK.js +140 -0
  12. package/dist/chunks/cli-error-BjQwvWtK.js.map +1 -0
  13. package/dist/chunks/config-BGP1jZJ4.js +167 -0
  14. package/dist/chunks/config-BGP1jZJ4.js.map +1 -0
  15. package/dist/chunks/config-manager-BkbjtN-H.js +133 -0
  16. package/dist/chunks/config-manager-BkbjtN-H.js.map +1 -0
  17. package/dist/chunks/database-BvAbD4sP.js +68 -0
  18. package/dist/chunks/database-BvAbD4sP.js.map +1 -0
  19. package/dist/chunks/database-setup-BYjIRAmT.js +253 -0
  20. package/dist/chunks/database-setup-BYjIRAmT.js.map +1 -0
  21. package/dist/chunks/exports-ij9sv4UM.js +7793 -0
  22. package/dist/chunks/exports-ij9sv4UM.js.map +1 -0
  23. package/dist/chunks/init-CZoN6soU.js +468 -0
  24. package/dist/chunks/init-CZoN6soU.js.map +1 -0
  25. package/dist/chunks/init-tui-BNzk_7Yx.js +1127 -0
  26. package/dist/chunks/init-tui-BNzk_7Yx.js.map +1 -0
  27. package/dist/chunks/logger-ZpJi7chw.js +38 -0
  28. package/dist/chunks/logger-ZpJi7chw.js.map +1 -0
  29. package/dist/chunks/main-tui-Cq1hLCx-.js +644 -0
  30. package/dist/chunks/main-tui-Cq1hLCx-.js.map +1 -0
  31. package/dist/chunks/manager-CvGX9qqe.js +1161 -0
  32. package/dist/chunks/manager-CvGX9qqe.js.map +1 -0
  33. package/dist/chunks/port-allocator-BRFzgH9b.js +749 -0
  34. package/dist/chunks/port-allocator-BRFzgH9b.js.map +1 -0
  35. package/dist/chunks/process-killer-CaUL7Kpl.js +87 -0
  36. package/dist/chunks/process-killer-CaUL7Kpl.js.map +1 -0
  37. package/dist/chunks/prompts-1QbE_bRr.js +128 -0
  38. package/dist/chunks/prompts-1QbE_bRr.js.map +1 -0
  39. package/dist/chunks/repo-cloner-CpOQjFSo.js +219 -0
  40. package/dist/chunks/repo-cloner-CpOQjFSo.js.map +1 -0
  41. package/dist/chunks/repo-detector-B_oj696o.js +66 -0
  42. package/dist/chunks/repo-detector-B_oj696o.js.map +1 -0
  43. package/dist/chunks/run-D23hg4xy.js +630 -0
  44. package/dist/chunks/run-D23hg4xy.js.map +1 -0
  45. package/dist/chunks/runner-logger-instance-nDWv2h2T.js +899 -0
  46. package/dist/chunks/runner-logger-instance-nDWv2h2T.js.map +1 -0
  47. package/dist/chunks/spinner-BJL9zWAJ.js +53 -0
  48. package/dist/chunks/spinner-BJL9zWAJ.js.map +1 -0
  49. package/dist/chunks/start-BygPCbvw.js +1708 -0
  50. package/dist/chunks/start-BygPCbvw.js.map +1 -0
  51. package/dist/chunks/start-traditional-uoLZXdxm.js +255 -0
  52. package/dist/chunks/start-traditional-uoLZXdxm.js.map +1 -0
  53. package/dist/chunks/status-cS8YwtUx.js +97 -0
  54. package/dist/chunks/status-cS8YwtUx.js.map +1 -0
  55. package/dist/chunks/theme-DhorI2Hb.js +44 -0
  56. package/dist/chunks/theme-DhorI2Hb.js.map +1 -0
  57. package/dist/chunks/upgrade-CT6w0lKp.js +323 -0
  58. package/dist/chunks/upgrade-CT6w0lKp.js.map +1 -0
  59. package/dist/chunks/useBuildState-CdBSu9y_.js +331 -0
  60. package/dist/chunks/useBuildState-CdBSu9y_.js.map +1 -0
  61. package/dist/cli/index.js +694 -0
  62. package/dist/cli/index.js.map +1 -0
  63. package/dist/index.js +14358 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/instrument.js +64226 -0
  66. package/dist/instrument.js.map +1 -0
  67. package/dist/templates.json +295 -0
  68. package/package.json +98 -0
  69. package/scripts/install-vendor-deps.js +34 -0
  70. package/scripts/install-vendor.js +167 -0
  71. package/scripts/prepare-release.js +71 -0
  72. package/templates/config.template.json +18 -0
  73. package/templates.json +295 -0
  74. package/vendor/ai-sdk-provider-claude-code-LOCAL.tgz +0 -0
  75. package/vendor/sentry-core-LOCAL.tgz +0 -0
  76. package/vendor/sentry-nextjs-LOCAL.tgz +0 -0
  77. package/vendor/sentry-node-LOCAL.tgz +0 -0
  78. 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