@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,1127 @@
1
+ // OpenBuilder CLI - Built with Rollup
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { existsSync, rmSync, realpathSync } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ import { randomBytes } from 'node:crypto';
6
+ import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
7
+ import { Fragment, useState, useRef, useEffect, useCallback } from 'react';
8
+ import { Box, Text, useApp, useStdout, useInput, render } from 'ink';
9
+ import { g as getVersionInfo, B as Banner } from './Banner-D4tqKfzA.js';
10
+ import { s as symbols, c as colors, l as layout } from './theme-DhorI2Hb.js';
11
+ import 'ink-text-input';
12
+ import 'node:events';
13
+ import 'chalk';
14
+ import { c as configManager } from './config-manager-BkbjtN-H.js';
15
+ import { i as isInsideMonorepo } from './repo-detector-B_oj696o.js';
16
+ import { isPnpmInstalled, cloneRepository, installDependencies, buildAgentCore } from './repo-cloner-CpOQjFSo.js';
17
+ import { p as pushDatabaseSchema, s as setupDatabase } from './database-setup-BYjIRAmT.js';
18
+ import { C as CLIError } from './cli-error-BjQwvWtK.js';
19
+ import 'node:child_process';
20
+ import 'node:url';
21
+ import 'conf';
22
+ import 'node:os';
23
+ import './logger-ZpJi7chw.js';
24
+ import './spinner-BJL9zWAJ.js';
25
+ import 'ora';
26
+ import './prompts-1QbE_bRr.js';
27
+ import 'inquirer';
28
+
29
+ /**
30
+ * Horizontal progress stepper - just the dots and labels
31
+ *
32
+ * ●───────────●───────────○───────────○
33
+ * Setup Install Database Finalize
34
+ */
35
+ function ProgressStepper({ steps }) {
36
+ const getStepColor = (status) => {
37
+ switch (status) {
38
+ case 'completed':
39
+ return colors.success;
40
+ case 'active':
41
+ return colors.cyan;
42
+ case 'error':
43
+ return colors.error;
44
+ default:
45
+ return colors.dimGray;
46
+ }
47
+ };
48
+ const getStepSymbol = (status) => {
49
+ switch (status) {
50
+ case 'error':
51
+ return symbols.errorDot;
52
+ case 'completed':
53
+ case 'active':
54
+ return symbols.filledDot;
55
+ default:
56
+ return symbols.hollowDot;
57
+ }
58
+ };
59
+ // Cell and connector sizing
60
+ const cellWidth = 10;
61
+ const connectorWidth = 7;
62
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", children: [jsx(Box, { children: steps.map((step, index) => (jsxs(Fragment, { children: [jsx(Box, { width: cellWidth, justifyContent: "center", children: jsx(Text, { color: getStepColor(step.status), children: getStepSymbol(step.status) }) }), index < steps.length - 1 && (jsx(Text, { color: colors.dimGray, children: symbols.horizontalLine.repeat(connectorWidth) }))] }, step.id))) }), jsx(Box, { children: steps.map((step, index) => (jsxs(Fragment, { children: [jsx(Box, { width: cellWidth, justifyContent: "center", children: jsx(Text, { color: step.status === 'pending' ? colors.dimGray : colors.gray, dimColor: step.status === 'pending', children: step.label }) }), index < steps.length - 1 && (jsx(Box, { width: connectorWidth }))] }, step.id))) })] }));
63
+ }
64
+
65
+ // Match the stepper width: cellWidth(10) * 4 + connectorWidth(7) * 3 = 61
66
+ const BOX_WIDTH = 61;
67
+ const BOX_INNER_WIDTH = BOX_WIDTH - 4; // Account for "│ " and " │"
68
+ /**
69
+ * TaskStream - Shows current step's tasks in a terminal-style box
70
+ *
71
+ * ┌───────────────────────────────────────────────────────────┐
72
+ * │ ⠋ Configuring database │
73
+ * │ └── Setting up Neon database...▌ │
74
+ * └───────────────────────────────────────────────────────────┘
75
+ */
76
+ function TaskStream({ stepId, tasks, onTypewriterComplete }) {
77
+ const [spinnerIndex, setSpinnerIndex] = useState(0);
78
+ const [cursorVisible, setCursorVisible] = useState(true);
79
+ const [typedChars, setTypedChars] = useState({});
80
+ const [completedTyping, setCompletedTyping] = useState(new Set());
81
+ const prevStepRef = useRef(stepId);
82
+ // Reset typed chars when step changes
83
+ useEffect(() => {
84
+ if (stepId !== prevStepRef.current) {
85
+ setTypedChars({});
86
+ setCompletedTyping(new Set());
87
+ prevStepRef.current = stepId;
88
+ }
89
+ }, [stepId]);
90
+ // Spinner animation - runs independently
91
+ useEffect(() => {
92
+ let mounted = true;
93
+ const animate = () => {
94
+ if (!mounted)
95
+ return;
96
+ setSpinnerIndex(prev => (prev + 1) % symbols.spinnerFrames.length);
97
+ setTimeout(animate, layout.spinnerInterval);
98
+ };
99
+ const timeout = setTimeout(animate, layout.spinnerInterval);
100
+ return () => {
101
+ mounted = false;
102
+ clearTimeout(timeout);
103
+ };
104
+ }, []);
105
+ // Cursor blink - runs independently
106
+ useEffect(() => {
107
+ let mounted = true;
108
+ const blink = () => {
109
+ if (!mounted)
110
+ return;
111
+ setCursorVisible(prev => !prev);
112
+ setTimeout(blink, 400);
113
+ };
114
+ const timeout = setTimeout(blink, 400);
115
+ return () => {
116
+ mounted = false;
117
+ clearTimeout(timeout);
118
+ };
119
+ }, []);
120
+ // Find tasks by status
121
+ const runningTask = tasks.find(t => t.status === 'running');
122
+ const failedTask = tasks.find(t => t.status === 'failed');
123
+ const completedTasks = tasks.filter(t => t.status === 'completed');
124
+ // Primary task: running > failed > last completed (to prevent visual jump)
125
+ const primaryTask = runningTask || failedTask || completedTasks[completedTasks.length - 1];
126
+ // Previous completed tasks (all except the one shown as primary)
127
+ const previousCompleted = primaryTask?.status === 'completed'
128
+ ? completedTasks.slice(0, -1)
129
+ : completedTasks;
130
+ // Build display lines
131
+ const lines = [];
132
+ // Main task line (running, failed, or last completed)
133
+ if (primaryTask) {
134
+ const isRunning = primaryTask.status === 'running';
135
+ const isCompleted = primaryTask.status === 'completed';
136
+ primaryTask.status === 'failed';
137
+ let prefix;
138
+ let prefixColor;
139
+ let textColor;
140
+ if (isRunning) {
141
+ prefix = `${symbols.spinnerFrames[spinnerIndex]} `;
142
+ prefixColor = colors.cyan;
143
+ textColor = colors.white;
144
+ }
145
+ else if (isCompleted) {
146
+ prefix = `${symbols.check} `;
147
+ prefixColor = colors.success;
148
+ textColor = colors.white;
149
+ }
150
+ else {
151
+ prefix = `${symbols.cross} `;
152
+ prefixColor = colors.error;
153
+ textColor = colors.error;
154
+ }
155
+ lines.push({
156
+ id: `main-${primaryTask.id}`,
157
+ text: primaryTask.label,
158
+ displayedText: primaryTask.label,
159
+ color: textColor,
160
+ prefix,
161
+ prefixColor,
162
+ showCursor: false,
163
+ });
164
+ }
165
+ // Previous completed tasks as subtasks - with green checkmark
166
+ previousCompleted.forEach((task, index) => {
167
+ const hasMore = primaryTask?.detail || primaryTask?.error || index < previousCompleted.length - 1;
168
+ const connector = hasMore ? '├──' : '└──';
169
+ lines.push({
170
+ id: `completed-${task.id}`,
171
+ text: task.label,
172
+ displayedText: task.label,
173
+ color: colors.white,
174
+ prefix: ` ${connector} `,
175
+ prefixColor: colors.dimGray,
176
+ showCursor: false,
177
+ checkmark: true,
178
+ });
179
+ });
180
+ // Detail line (no typewriter for progress updates - show immediately)
181
+ if (primaryTask?.detail && primaryTask.status === 'running') {
182
+ lines.push({
183
+ id: `detail-${primaryTask.id}`,
184
+ text: primaryTask.detail,
185
+ displayedText: primaryTask.detail, // Show full text immediately for progress updates
186
+ color: colors.dimGray,
187
+ prefix: ' └── ',
188
+ prefixColor: colors.dimGray,
189
+ showCursor: false, // No cursor for instant updates
190
+ });
191
+ }
192
+ // Error line (typewriter effect)
193
+ if (primaryTask?.error && primaryTask.status === 'failed') {
194
+ const errorId = `error-${primaryTask.id}`;
195
+ const currentChars = typedChars[errorId] || 0;
196
+ const displayedText = primaryTask.error.slice(0, currentChars);
197
+ const isTyping = currentChars < primaryTask.error.length;
198
+ lines.push({
199
+ id: errorId,
200
+ text: primaryTask.error,
201
+ displayedText,
202
+ color: colors.error,
203
+ prefix: ' └── ',
204
+ prefixColor: colors.dimGray,
205
+ showCursor: isTyping && cursorVisible,
206
+ });
207
+ }
208
+ // Typewriter animation - only for errors now (details show instantly for progress)
209
+ const errorToType = primaryTask?.error && primaryTask.status === 'failed' ? primaryTask.error : null;
210
+ const textToType = errorToType;
211
+ const typeId = errorToType ? `error-${primaryTask?.id}` : null;
212
+ useEffect(() => {
213
+ if (!typeId || !textToType)
214
+ return;
215
+ const currentChars = typedChars[typeId] || 0;
216
+ // Check if we just finished typing
217
+ if (currentChars >= textToType.length) {
218
+ // Fire callback once when typing completes
219
+ if (!completedTyping.has(typeId) && onTypewriterComplete && primaryTask) {
220
+ setCompletedTyping(prev => new Set(prev).add(typeId));
221
+ onTypewriterComplete(primaryTask.id);
222
+ }
223
+ return;
224
+ }
225
+ // Continue typing animation
226
+ const timeout = setTimeout(() => {
227
+ setTypedChars(prev => ({
228
+ ...prev,
229
+ [typeId]: (prev[typeId] || 0) + 1,
230
+ }));
231
+ }, 30);
232
+ return () => clearTimeout(timeout);
233
+ }, [typeId, textToType, typedChars[typeId || ''], completedTyping, onTypewriterComplete, primaryTask]);
234
+ // Empty state - show empty box to maintain layout
235
+ if (lines.length === 0) {
236
+ return (jsx(Box, { flexDirection: "column", alignItems: "center", marginTop: 1, children: jsxs(Box, { flexDirection: "column", width: BOX_WIDTH, children: [jsxs(Text, { color: colors.dimGray, children: ["\u250C", '─'.repeat(BOX_WIDTH - 2), "\u2510"] }), jsxs(Box, { children: [jsx(Text, { color: colors.dimGray, children: "\u2502" }), jsx(Text, { children: ' '.repeat(BOX_WIDTH - 2) }), jsx(Text, { color: colors.dimGray, children: "\u2502" })] }), jsxs(Text, { color: colors.dimGray, children: ["\u2514", '─'.repeat(BOX_WIDTH - 2), "\u2518"] })] }) }));
237
+ }
238
+ return (jsx(Box, { flexDirection: "column", alignItems: "center", marginTop: 1, children: jsxs(Box, { flexDirection: "column", width: BOX_WIDTH, children: [jsxs(Text, { color: colors.dimGray, children: ["\u250C", '─'.repeat(BOX_WIDTH - 2), "\u2510"] }), lines.map(line => {
239
+ const checkmarkStr = line.checkmark ? `${symbols.check} ` : '';
240
+ const content = `${line.prefix}${checkmarkStr}${line.displayedText}`;
241
+ const cursor = line.showCursor ? '▌' : '';
242
+ const paddingNeeded = Math.max(0, BOX_INNER_WIDTH - content.length - cursor.length);
243
+ return (jsxs(Box, { children: [jsx(Text, { color: colors.dimGray, children: "\u2502 " }), jsx(Text, { color: line.prefixColor, children: line.prefix }), line.checkmark && jsxs(Text, { color: colors.success, children: [symbols.check, " "] }), jsx(Text, { color: line.color, children: line.displayedText }), line.showCursor && jsx(Text, { color: colors.cyan, children: "\u258C" }), jsx(Text, { children: ' '.repeat(paddingNeeded) }), jsx(Text, { color: colors.dimGray, children: " \u2502" })] }, line.id));
244
+ }), jsxs(Text, { color: colors.dimGray, children: ["\u2514", '─'.repeat(BOX_WIDTH - 2), "\u2518"] })] }) }));
245
+ }
246
+
247
+ /**
248
+ * Configuration summary display
249
+ * ───────────────────────────────────
250
+ * Workspace ~/openbuilder-workspace
251
+ * Server http://localhost:3000
252
+ * Runner local
253
+ * ───────────────────────────────────
254
+ */
255
+ function ConfigSummary({ items, title }) {
256
+ const dividerWidth = 40;
257
+ const divider = symbols.horizontalLine.repeat(dividerWidth);
258
+ // Find the longest label for alignment
259
+ const maxLabelLength = Math.max(...items.map(item => item.label.length));
260
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", children: [jsx(Text, { color: colors.dimGray, children: divider }), title && (jsx(Box, { marginTop: 1, marginBottom: 1, children: jsx(Text, { color: colors.white, bold: true, children: title }) })), jsx(Box, { flexDirection: "column", alignItems: "flex-start", marginTop: title ? 0 : 1, children: items.map((item, index) => (jsxs(Box, { children: [jsx(Text, { color: colors.gray, children: item.label.padEnd(maxLabelLength + 3) }), jsx(Text, { color: colors.cyan, children: item.value })] }, index))) }), jsx(Box, { marginTop: 1, children: jsx(Text, { color: colors.dimGray, children: divider }) })] }));
261
+ }
262
+ /**
263
+ * Next steps display for completion screen
264
+ */
265
+ function NextSteps({ command, url }) {
266
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [jsxs(Box, { children: [jsx(Text, { color: colors.gray, children: "Run: " }), jsx(Text, { color: colors.cyan, bold: true, children: command })] }), jsxs(Box, { children: [jsx(Text, { color: colors.gray, children: "Open: " }), jsx(Text, { color: colors.cyan, children: url })] })] }));
267
+ }
268
+ /**
269
+ * Error display with message and recovery suggestions
270
+ */
271
+ function ErrorSummary({ message, suggestions }) {
272
+ const dividerWidth = 44;
273
+ const divider = symbols.horizontalLine.repeat(dividerWidth);
274
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", children: [jsx(Text, { color: colors.dimGray, children: divider }), message && (jsx(Box, { marginTop: 1, marginBottom: 1, children: jsxs(Text, { color: colors.error, bold: true, children: [symbols.cross, " ", message] }) })), jsx(Box, { flexDirection: "column", alignItems: "flex-start", marginTop: message ? 0 : 1, children: suggestions.map((suggestion, index) => (jsx(Box, { children: jsx(Text, { color: suggestion.startsWith(' ') ? colors.cyan : colors.gray, children: suggestion }) }, index))) }), jsx(Box, { marginTop: 1, children: jsx(Text, { color: colors.dimGray, children: divider }) })] }));
275
+ }
276
+
277
+ /**
278
+ * Compact build error viewer with scrolling and copy support
279
+ * Displays in the center area, half-width, with scrollable content
280
+ */
281
+ function BuildErrorView({ title, errorLines, suggestions = [], onExit }) {
282
+ const { exit } = useApp();
283
+ const { stdout } = useStdout();
284
+ const terminalWidth = stdout?.columns || 80;
285
+ const [scrollOffset, setScrollOffset] = useState(0);
286
+ const [showCopiedMessage, setShowCopiedMessage] = useState(false);
287
+ // Fixed dimensions for compact view
288
+ const boxWidth = Math.min(70, Math.floor(terminalWidth * 0.6));
289
+ const maxVisibleLines = 12; // Show 12 lines of errors max
290
+ // Calculate max scroll
291
+ const maxScroll = Math.max(0, errorLines.length - maxVisibleLines);
292
+ // Get visible lines
293
+ const visibleLines = errorLines.slice(scrollOffset, scrollOffset + maxVisibleLines);
294
+ // Copy all error content to clipboard
295
+ const copyToClipboard = useCallback(async () => {
296
+ const content = [
297
+ `Build Error: ${title}`,
298
+ '─'.repeat(60),
299
+ ...errorLines,
300
+ '─'.repeat(60),
301
+ '',
302
+ ...suggestions,
303
+ ].join('\n');
304
+ try {
305
+ const { exec } = await import('child_process');
306
+ // Detect platform and use appropriate clipboard command
307
+ const platform = process.platform;
308
+ let clipboardCmd;
309
+ if (platform === 'darwin') {
310
+ clipboardCmd = 'pbcopy';
311
+ }
312
+ else if (platform === 'win32') {
313
+ clipboardCmd = 'clip';
314
+ }
315
+ else {
316
+ clipboardCmd = 'xclip -selection clipboard';
317
+ }
318
+ // Write to clipboard
319
+ const child = exec(clipboardCmd);
320
+ child.stdin?.write(content);
321
+ child.stdin?.end();
322
+ setShowCopiedMessage(true);
323
+ // Show success message then exit after delay
324
+ setTimeout(() => {
325
+ if (onExit) {
326
+ onExit();
327
+ }
328
+ else {
329
+ exit();
330
+ }
331
+ }, 1500);
332
+ }
333
+ catch (error) {
334
+ // Clipboard failed - still show message
335
+ setShowCopiedMessage(true);
336
+ setTimeout(() => setShowCopiedMessage(false), 2000);
337
+ }
338
+ }, [title, errorLines, suggestions, onExit, exit]);
339
+ // Handle keyboard input
340
+ useInput((input, key) => {
341
+ if (input === 'c' || input === 'C') {
342
+ copyToClipboard();
343
+ }
344
+ else if (key.upArrow) {
345
+ setScrollOffset(prev => Math.max(0, prev - 1));
346
+ }
347
+ else if (key.downArrow) {
348
+ setScrollOffset(prev => Math.min(maxScroll, prev + 1));
349
+ }
350
+ else if (key.pageUp) {
351
+ setScrollOffset(prev => Math.max(0, prev - maxVisibleLines));
352
+ }
353
+ else if (key.pageDown) {
354
+ setScrollOffset(prev => Math.min(maxScroll, prev + maxVisibleLines));
355
+ }
356
+ else if (input === 'q' || key.escape) {
357
+ if (onExit) {
358
+ onExit();
359
+ }
360
+ else {
361
+ exit();
362
+ }
363
+ }
364
+ });
365
+ // Color error lines based on content
366
+ const getLineColor = (line) => {
367
+ if (/error|Error|ERR!/i.test(line))
368
+ return colors.error;
369
+ if (/warning|warn/i.test(line))
370
+ return colors.warning;
371
+ if (/:\d+:\d+/.test(line))
372
+ return colors.cyan;
373
+ if (line.startsWith('─') || line.startsWith('openbuilder:'))
374
+ return colors.dimGray;
375
+ return colors.gray;
376
+ };
377
+ const divider = symbols.horizontalLine.repeat(boxWidth - 2);
378
+ const hasScroll = errorLines.length > maxVisibleLines;
379
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", width: "100%", children: [jsx(Text, { color: colors.dimGray, children: divider }), jsx(Box, { marginTop: 1, marginBottom: 1, width: boxWidth, justifyContent: "center", children: jsxs(Text, { color: colors.error, bold: true, children: [symbols.cross, " ", title] }) }), jsxs(Box, { flexDirection: "column", width: boxWidth, borderStyle: "round", borderColor: colors.error, paddingX: 1, paddingY: 0, children: [hasScroll && (jsx(Box, { justifyContent: "flex-end", marginBottom: 0, children: jsxs(Text, { color: colors.dimGray, dimColor: true, children: ["[", scrollOffset + 1, "-", Math.min(scrollOffset + maxVisibleLines, errorLines.length), "/", errorLines.length, "] \u2191\u2193"] }) })), visibleLines.length > 0 ? (visibleLines.map((line, index) => {
380
+ const truncatedLine = line.length > boxWidth - 4
381
+ ? line.substring(0, boxWidth - 7) + '...'
382
+ : line;
383
+ return (jsx(Text, { color: getLineColor(line), wrap: "truncate", children: truncatedLine }, index));
384
+ })) : (jsx(Text, { color: colors.dimGray, children: "No error details captured" }))] }), suggestions.length > 0 && (jsx(Box, { flexDirection: "column", marginTop: 1, width: boxWidth, children: suggestions.map((suggestion, index) => (jsx(Text, { color: suggestion.startsWith(' ') ? colors.cyan : colors.gray, children: suggestion }, index))) })), jsx(Box, { marginTop: 1, children: jsx(Text, { color: colors.dimGray, children: divider }) }), jsx(Box, { marginTop: 1, justifyContent: "center", children: showCopiedMessage ? (jsxs(Text, { color: colors.success, bold: true, children: [symbols.check, " Copied to clipboard! Exiting..."] })) : (jsxs(Box, { children: [jsx(Text, { color: colors.dimGray, children: "[" }), jsx(Text, { color: colors.success, bold: true, children: "c" }), jsx(Text, { color: colors.dimGray, children: "]" }), jsx(Text, { color: colors.success, children: "copy & exit" }), jsx(Text, { children: " " }), jsx(Text, { color: colors.dimGray, children: "[" }), jsx(Text, { color: colors.cyan, children: "q" }), jsx(Text, { color: colors.dimGray, children: "]" }), jsx(Text, { color: colors.gray, children: "quit" }), hasScroll && (jsxs(Fragment$1, { children: [jsx(Text, { children: " " }), jsx(Text, { color: colors.dimGray, children: "[" }), jsx(Text, { color: colors.cyan, children: "\u2191\u2193" }), jsx(Text, { color: colors.dimGray, children: "]" }), jsx(Text, { color: colors.gray, children: "scroll" })] }))] })) })] }));
385
+ }
386
+
387
+ const INITIAL_STEPS = [
388
+ { id: 'repo', label: 'Clone', status: 'pending' },
389
+ { id: 'build', label: 'Build', status: 'pending' },
390
+ { id: 'database', label: 'Configure', status: 'pending' },
391
+ { id: 'ready', label: 'Finish', status: 'pending' },
392
+ ];
393
+ // Tasks now have a stepId to associate them with a step
394
+ const INITIAL_TASKS = [
395
+ { id: 'clone', label: 'Cloning repository', status: 'pending', stepId: 'repo' },
396
+ { id: 'deps', label: 'Installing dependencies', status: 'pending', stepId: 'build' },
397
+ { id: 'build', label: 'Building packages', status: 'pending', stepId: 'build' },
398
+ { id: 'database', label: 'Configuring database', status: 'pending', stepId: 'database' },
399
+ { id: 'config', label: 'Saving configuration', status: 'pending', stepId: 'ready' },
400
+ { id: 'services', label: 'Building services', status: 'pending', stepId: 'ready' },
401
+ ];
402
+ function useInitFlow() {
403
+ const [state, setState] = useState({
404
+ phase: 'repo',
405
+ steps: INITIAL_STEPS,
406
+ tasks: INITIAL_TASKS,
407
+ config: [],
408
+ error: null,
409
+ isComplete: false,
410
+ });
411
+ // Step management
412
+ const setStepStatus = useCallback((stepId, status) => {
413
+ setState(prev => ({
414
+ ...prev,
415
+ steps: prev.steps.map(step => step.id === stepId ? { ...step, status } : step),
416
+ }));
417
+ }, []);
418
+ const activateStep = useCallback((stepId) => {
419
+ setState(prev => ({
420
+ ...prev,
421
+ phase: stepId,
422
+ steps: prev.steps.map(step => step.id === stepId ? { ...step, status: 'active' } : step),
423
+ }));
424
+ }, []);
425
+ const completeStep = useCallback((stepId) => {
426
+ setStepStatus(stepId, 'completed');
427
+ }, [setStepStatus]);
428
+ const failStep = useCallback((stepId) => {
429
+ setStepStatus(stepId, 'error');
430
+ }, [setStepStatus]);
431
+ // Task management
432
+ const setTaskStatus = useCallback((taskId, status, detail) => {
433
+ setState(prev => ({
434
+ ...prev,
435
+ tasks: prev.tasks.map(task => task.id === taskId ? { ...task, status, detail: detail ?? task.detail } : task),
436
+ }));
437
+ }, []);
438
+ const startTask = useCallback((taskId, detail) => {
439
+ setTaskStatus(taskId, 'running', detail);
440
+ }, [setTaskStatus]);
441
+ const completeTask = useCallback((taskId) => {
442
+ setTaskStatus(taskId, 'completed');
443
+ }, [setTaskStatus]);
444
+ const failTask = useCallback((taskId, error) => {
445
+ setState(prev => ({
446
+ ...prev,
447
+ tasks: prev.tasks.map(task => task.id === taskId ? { ...task, status: 'failed', error } : task),
448
+ }));
449
+ }, []);
450
+ // Get tasks for a specific step
451
+ const getTasksForStep = useCallback((stepId) => {
452
+ return state.tasks.filter(task => task.stepId === stepId);
453
+ }, [state.tasks]);
454
+ // Get tasks for the currently active step
455
+ const getActiveStepTasks = useCallback(() => {
456
+ return state.tasks.filter(task => task.stepId === state.phase);
457
+ }, [state.tasks, state.phase]);
458
+ // Config management
459
+ const setConfig = useCallback((items) => {
460
+ setState(prev => ({
461
+ ...prev,
462
+ config: items,
463
+ }));
464
+ }, []);
465
+ // Error management
466
+ const setError = useCallback((message, suggestions) => {
467
+ setState(prev => ({
468
+ ...prev,
469
+ error: { message, suggestions },
470
+ }));
471
+ }, []);
472
+ const setBuildError = useCallback((message, errorLines, suggestions) => {
473
+ setState(prev => ({
474
+ ...prev,
475
+ error: {
476
+ message,
477
+ suggestions,
478
+ buildError: {
479
+ errorLines,
480
+ suggestions,
481
+ }
482
+ },
483
+ }));
484
+ }, []);
485
+ const clearError = useCallback(() => {
486
+ setState(prev => ({
487
+ ...prev,
488
+ error: null,
489
+ }));
490
+ }, []);
491
+ // Completion
492
+ const markComplete = useCallback(() => {
493
+ setState(prev => ({
494
+ ...prev,
495
+ isComplete: true,
496
+ phase: 'ready',
497
+ steps: prev.steps.map(step => ({ ...step, status: 'completed' })),
498
+ }));
499
+ }, []);
500
+ // Reset
501
+ const reset = useCallback(() => {
502
+ setState({
503
+ phase: 'repo',
504
+ steps: INITIAL_STEPS,
505
+ tasks: INITIAL_TASKS,
506
+ config: [],
507
+ error: null,
508
+ isComplete: false,
509
+ });
510
+ }, []);
511
+ return {
512
+ state,
513
+ setStepStatus,
514
+ activateStep,
515
+ completeStep,
516
+ failStep,
517
+ setTaskStatus,
518
+ startTask,
519
+ completeTask,
520
+ failTask,
521
+ getTasksForStep,
522
+ getActiveStepTasks,
523
+ setConfig,
524
+ setError,
525
+ setBuildError,
526
+ clearError,
527
+ markComplete,
528
+ reset,
529
+ };
530
+ }
531
+
532
+ /**
533
+ * Main init screen - fullscreen centered TUI
534
+ * Shows progress stepper with tasks appearing under the active step
535
+ */
536
+ function InitScreen({ onInit, onComplete, onError }) {
537
+ const { exit } = useApp();
538
+ const { stdout } = useStdout();
539
+ const flow = useInitFlow();
540
+ const [finalConfig, setFinalConfig] = useState(null);
541
+ // Get version info
542
+ const versionInfo = getVersionInfo();
543
+ // Calculate vertical centering
544
+ const terminalHeight = stdout?.rows || 24;
545
+ const contentHeight = 20;
546
+ const topPadding = Math.max(0, Math.floor((terminalHeight - contentHeight) / 3));
547
+ // Run init flow
548
+ useEffect(() => {
549
+ const callbacks = {
550
+ activateStep: flow.activateStep,
551
+ completeStep: flow.completeStep,
552
+ failStep: flow.failStep,
553
+ startTask: flow.startTask,
554
+ completeTask: flow.completeTask,
555
+ failTask: flow.failTask,
556
+ skipTask: (taskId) => {
557
+ flow.setTaskStatus(taskId, 'completed');
558
+ },
559
+ updateTaskLabel: (taskId, label) => {
560
+ flow.setTaskStatus(taskId, flow.state.tasks.find(t => t.id === taskId)?.status || 'pending');
561
+ },
562
+ setError: flow.setError,
563
+ setBuildError: flow.setBuildError,
564
+ };
565
+ onInit(callbacks)
566
+ .then((config) => {
567
+ setFinalConfig(config);
568
+ // Set config summary items
569
+ const configItems = [
570
+ { label: 'Workspace', value: config.workspace },
571
+ { label: 'Server', value: config.apiUrl },
572
+ { label: 'Runner', value: config.runnerId },
573
+ ];
574
+ if (config.monorepoPath) {
575
+ configItems.push({ label: 'Repository', value: config.monorepoPath });
576
+ }
577
+ flow.setConfig(configItems);
578
+ flow.markComplete();
579
+ if (onComplete) {
580
+ onComplete(config);
581
+ }
582
+ })
583
+ .catch((error) => {
584
+ if (onError) {
585
+ onError(error);
586
+ }
587
+ });
588
+ }, []);
589
+ const { state } = flow;
590
+ // Get tasks for current step only, mapped to StreamTask format
591
+ const currentStepTasks = flow.getActiveStepTasks().map(task => ({
592
+ id: task.id,
593
+ label: task.label,
594
+ status: task.status,
595
+ detail: task.detail,
596
+ error: task.error,
597
+ }));
598
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", paddingTop: topPadding, children: [jsx(Banner, {}), jsx(Box, { marginTop: 1, children: jsx(Text, { color: colors.gray, dimColor: true, children: versionInfo.display }) }), jsx(Box, { marginTop: 1 }), jsx(ProgressStepper, { steps: state.steps }), !state.isComplete && !state.error && (jsx(TaskStream, { stepId: state.phase, tasks: currentStepTasks })), state.error && state.error.buildError && (jsx(BuildErrorView, { title: state.error.message, errorLines: state.error.buildError.errorLines, suggestions: state.error.buildError.suggestions, onExit: () => exit() })), state.error && !state.error.buildError && (jsx(Box, { marginTop: 2, children: jsx(ErrorSummary, { message: state.error.message, suggestions: state.error.suggestions }) })), state.isComplete && !state.error && (jsxs(Box, { marginTop: 2, flexDirection: "column", alignItems: "center", children: [jsxs(Text, { color: colors.success, bold: true, children: [symbols.check, " Setup complete!"] }), jsx(Box, { marginTop: 1, flexDirection: "column", children: state.tasks.filter(t => t.status === 'completed').map(task => (jsxs(Box, { children: [jsx(Text, { color: colors.success, children: symbols.check }), jsxs(Text, { color: colors.gray, children: [" ", task.label] })] }, task.id))) }), jsx(Box, { marginTop: 1 }), jsx(ConfigSummary, { items: state.config }), jsx(NextSteps, { command: "openbuilder run", url: "http://localhost:3000" })] }))] }));
599
+ }
600
+
601
+ /**
602
+ * Post-init prompt asking if user wants to start OpenBuilder now
603
+ */
604
+ function StartPromptScreen({ onSelect }) {
605
+ const { stdout } = useStdout();
606
+ // Calculate vertical centering
607
+ const terminalHeight = stdout?.rows || 24;
608
+ const contentHeight = 14;
609
+ const topPadding = Math.max(0, Math.floor((terminalHeight - contentHeight) / 3));
610
+ useInput((input, key) => {
611
+ const char = input.toLowerCase();
612
+ // Enter = start
613
+ if (key.return) {
614
+ onSelect(true);
615
+ return;
616
+ }
617
+ // Y = start
618
+ if (char === 'y') {
619
+ onSelect(true);
620
+ return;
621
+ }
622
+ // N = don't start
623
+ if (char === 'n') {
624
+ onSelect(false);
625
+ return;
626
+ }
627
+ // Escape = don't start
628
+ if (key.escape) {
629
+ onSelect(false);
630
+ return;
631
+ }
632
+ });
633
+ return (jsxs(Box, { flexDirection: "column", alignItems: "center", paddingTop: topPadding, children: [jsx(Banner, {}), jsx(Box, { marginTop: 2 }), jsx(Box, { flexDirection: "column", alignItems: "center", children: jsxs(Text, { color: colors.success, bold: true, children: [symbols.check, " OpenBuilder is ready!"] }) }), jsx(Box, { marginTop: 2 }), jsxs(Box, { flexDirection: "column", alignItems: "center", children: [jsx(Text, { color: colors.white, children: "Start OpenBuilder now?" }), jsx(Box, { marginTop: 1, children: jsxs(Text, { color: colors.dimGray, children: ["Press ", jsx(Text, { color: colors.cyan, bold: true, children: "Y" }), " or ", jsx(Text, { color: colors.cyan, bold: true, children: "Enter" }), " to start, ", jsx(Text, { color: colors.cyan, bold: true, children: "N" }), " or ", jsx(Text, { color: colors.cyan, bold: true, children: "Esc" }), " to exit"] }) })] }), jsx(Box, { marginTop: 2 }), jsx(Box, { flexDirection: "column", alignItems: "center", children: jsxs(Text, { color: colors.dimGray, children: ["To start later, run: ", jsx(Text, { color: colors.cyan, children: "openbuilder run" })] }) })] }));
634
+ }
635
+
636
+ /**
637
+ * Init App component that manages the init flow screens
638
+ */
639
+ function InitApp({ onInit, onComplete, onError }) {
640
+ const [screen, setScreen] = useState('init');
641
+ const [config, setConfig] = useState(null);
642
+ const handleInitComplete = (initConfig) => {
643
+ setConfig(initConfig);
644
+ // Show the start prompt screen
645
+ setScreen('prompt');
646
+ };
647
+ const handleStartChoice = (shouldStart) => {
648
+ if (config) {
649
+ onComplete(config, shouldStart);
650
+ }
651
+ };
652
+ if (screen === 'prompt' && config) {
653
+ return jsx(StartPromptScreen, { onSelect: handleStartChoice });
654
+ }
655
+ return (jsx(InitScreen, { onInit: onInit, onComplete: handleInitComplete, onError: onError }));
656
+ }
657
+ /**
658
+ * Render the TUI init screen
659
+ * Returns a promise that resolves with config and whether to start
660
+ */
661
+ async function runInitTUI(options) {
662
+ return new Promise((resolve, reject) => {
663
+ let result = null;
664
+ let error = null;
665
+ const { unmount, waitUntilExit } = render(jsx(InitApp, { onInit: options.onInit, onComplete: (config, shouldStart) => {
666
+ result = { config, shouldStart };
667
+ // Give time for final render before unmounting
668
+ setTimeout(() => {
669
+ unmount();
670
+ }, 100);
671
+ }, onError: (err) => {
672
+ error = err;
673
+ // Keep error displayed, don't auto-unmount
674
+ } }), {
675
+ exitOnCtrlC: true,
676
+ });
677
+ waitUntilExit().then(() => {
678
+ if (error) {
679
+ reject(error);
680
+ }
681
+ else if (result) {
682
+ resolve(result);
683
+ }
684
+ else {
685
+ reject(new Error('Init cancelled'));
686
+ }
687
+ });
688
+ });
689
+ }
690
+
691
+ /**
692
+ * TUI-based init command with beautiful centered interface
693
+ * Uses Ink for React-based terminal rendering
694
+ */
695
+ /**
696
+ * Generate a secure random secret
697
+ */
698
+ function generateSecret() {
699
+ return randomBytes(32).toString('hex');
700
+ }
701
+ /**
702
+ * Check if a path is or contains the current working directory
703
+ */
704
+ function isCurrentWorkingDirectory(targetPath) {
705
+ try {
706
+ const cwd = realpathSync(process.cwd());
707
+ const target = realpathSync(resolve(targetPath));
708
+ return cwd === target || cwd.startsWith(target + '/');
709
+ }
710
+ catch {
711
+ const cwd = process.cwd();
712
+ const target = resolve(targetPath);
713
+ return cwd === target || cwd.startsWith(target + '/');
714
+ }
715
+ }
716
+ /**
717
+ * Normalize URL by adding protocol if missing
718
+ */
719
+ function normalizeUrl(url) {
720
+ if (url.match(/^https?:\/\//i))
721
+ return url;
722
+ if (url.match(/^(localhost|127\.0\.0\.1)(:|\/|$)/i)) {
723
+ return `http://${url}`;
724
+ }
725
+ return `https://${url}`;
726
+ }
727
+ /**
728
+ * Get default workspace path
729
+ */
730
+ function getDefaultWorkspace() {
731
+ return join(process.cwd(), 'openbuilder-workspace');
732
+ }
733
+ /**
734
+ * Get default monorepo clone path
735
+ */
736
+ function getDefaultMonorepoPath() {
737
+ return join(process.cwd(), 'openbuilder');
738
+ }
739
+ /**
740
+ * Sleep utility for deliberate pacing
741
+ */
742
+ function sleep(ms) {
743
+ return new Promise(resolve => setTimeout(resolve, ms));
744
+ }
745
+ /**
746
+ * Run the TUI-based init command
747
+ */
748
+ async function initTUICommand(options) {
749
+ // Clear screen for fullscreen experience
750
+ console.clear();
751
+ try {
752
+ const { shouldStart } = await runInitTUI({
753
+ onInit: async (callbacks) => {
754
+ return executeInitFlow(options, callbacks);
755
+ },
756
+ });
757
+ if (shouldStart) {
758
+ console.clear();
759
+ console.log('\n Starting OpenBuilder...\n');
760
+ // Import and run the start command (full TUI with web app + runner)
761
+ const { startCommand } = await import('./start-BygPCbvw.js');
762
+ await startCommand({});
763
+ }
764
+ else {
765
+ console.clear();
766
+ console.log('\n ✨ OpenBuilder is ready!\n');
767
+ console.log(' To start later, run:\n');
768
+ console.log(' openbuilder run\n');
769
+ console.log(' Then open: http://localhost:3000\n');
770
+ }
771
+ }
772
+ catch (error) {
773
+ // Error was already displayed in TUI
774
+ console.log('\n');
775
+ process.exit(1);
776
+ }
777
+ }
778
+ /**
779
+ * Execute the init flow, calling callbacks to update UI
780
+ */
781
+ async function executeInitFlow(options, callbacks) {
782
+ const { activateStep, completeStep, failStep, startTask, completeTask, failTask, setError, setBuildError } = callbacks;
783
+ let monorepoPath;
784
+ let databaseUrl;
785
+ const workspace = options.workspace || getDefaultWorkspace();
786
+ const generatedSecret = options.secret || generateSecret();
787
+ const apiUrl = normalizeUrl(options.url || 'http://localhost:3000');
788
+ // ============================================
789
+ // PHASE 1: Repository
790
+ // ============================================
791
+ activateStep('repo');
792
+ await sleep(layout.stepTransitionDelay);
793
+ // Reset config if exists
794
+ if (configManager.isInitialized()) {
795
+ configManager.reset();
796
+ }
797
+ // Check for existing monorepo
798
+ startTask('clone', 'Checking for repository...');
799
+ await sleep(300);
800
+ const repoCheck = await isInsideMonorepo();
801
+ if (repoCheck.inside && repoCheck.root) {
802
+ monorepoPath = repoCheck.root;
803
+ completeTask('clone');
804
+ await sleep(layout.taskCompletionDelay);
805
+ }
806
+ else {
807
+ // Need to clone
808
+ const hasPnpm = await isPnpmInstalled();
809
+ if (!hasPnpm) {
810
+ failTask('clone', 'pnpm not found');
811
+ failStep('repo');
812
+ setError('pnpm is required', [
813
+ 'Install pnpm: npm install -g pnpm',
814
+ 'Then retry: openbuilder init -y',
815
+ ]);
816
+ throw new CLIError({
817
+ code: 'DEPENDENCIES_INSTALL_FAILED',
818
+ message: 'pnpm is not installed',
819
+ });
820
+ }
821
+ const clonePath = getDefaultMonorepoPath();
822
+ // Clean up existing installation
823
+ if (existsSync(clonePath)) {
824
+ if (isCurrentWorkingDirectory(clonePath)) {
825
+ failTask('clone', 'Cannot remove current directory');
826
+ failStep('repo');
827
+ setError('Cannot remove current directory', [
828
+ 'Run from a different directory',
829
+ 'Or manually remove: rm -rf ' + clonePath,
830
+ ]);
831
+ throw new CLIError({
832
+ code: 'CONFIG_INVALID',
833
+ message: 'Cannot remove current working directory',
834
+ });
835
+ }
836
+ rmSync(clonePath, { recursive: true, force: true });
837
+ }
838
+ try {
839
+ // Clone repository
840
+ startTask('clone', 'Cloning from GitHub...');
841
+ monorepoPath = await cloneRepository({
842
+ targetPath: clonePath,
843
+ branch: options.branch || 'main',
844
+ silent: true, // Suppress console output in TUI mode
845
+ });
846
+ completeTask('clone');
847
+ await sleep(layout.taskCompletionDelay);
848
+ }
849
+ catch (error) {
850
+ failTask('clone', 'Clone failed');
851
+ failStep('repo');
852
+ setError('Failed to clone repository', [
853
+ 'Check your internet connection',
854
+ 'Verify git is installed: git --version',
855
+ 'Try: git clone https://github.com/codyde/openbuilder.git',
856
+ ]);
857
+ throw error;
858
+ }
859
+ }
860
+ completeStep('repo');
861
+ await sleep(layout.stepTransitionDelay);
862
+ // ============================================
863
+ // PHASE 2: Build
864
+ // ============================================
865
+ activateStep('build');
866
+ await sleep(layout.stepTransitionDelay);
867
+ // Install dependencies
868
+ startTask('deps', 'Running pnpm install...');
869
+ try {
870
+ await installDependencies(monorepoPath, true); // silent mode
871
+ completeTask('deps');
872
+ await sleep(layout.taskCompletionDelay);
873
+ }
874
+ catch (error) {
875
+ failTask('deps', 'Install failed');
876
+ failStep('build');
877
+ setError('Failed to install dependencies', [
878
+ 'Check pnpm is installed: pnpm --version',
879
+ 'Try manually: cd ' + monorepoPath + ' && pnpm install',
880
+ ]);
881
+ throw error;
882
+ }
883
+ // Build packages
884
+ startTask('build', '@openbuilder/agent-core');
885
+ try {
886
+ await buildAgentCore(monorepoPath, true); // silent mode
887
+ completeTask('build');
888
+ await sleep(layout.taskCompletionDelay);
889
+ }
890
+ catch (error) {
891
+ failTask('build', 'Build failed');
892
+ failStep('build');
893
+ setError('Failed to build packages', [
894
+ 'Try manually: cd ' + monorepoPath + ' && pnpm build',
895
+ ]);
896
+ throw error;
897
+ }
898
+ completeStep('build');
899
+ await sleep(layout.stepTransitionDelay);
900
+ // ============================================
901
+ // PHASE 3: Database
902
+ // ============================================
903
+ activateStep('database');
904
+ await sleep(layout.stepTransitionDelay);
905
+ startTask('database', 'Setting up Neon database...');
906
+ // Handle database options
907
+ const dbOption = options.database;
908
+ const isConnectionString = typeof dbOption === 'string' &&
909
+ (dbOption.startsWith('postgres://') || dbOption.startsWith('postgresql://'));
910
+ try {
911
+ if (isConnectionString) {
912
+ databaseUrl = dbOption;
913
+ await pushDatabaseSchema(monorepoPath, databaseUrl, true); // silent mode
914
+ }
915
+ else {
916
+ // Default: setup Neon database
917
+ databaseUrl = await setupDatabase(monorepoPath, true) || undefined; // silent mode
918
+ if (databaseUrl) {
919
+ await pushDatabaseSchema(monorepoPath, databaseUrl, true); // silent mode
920
+ }
921
+ }
922
+ completeTask('database');
923
+ await sleep(layout.taskCompletionDelay);
924
+ }
925
+ catch (error) {
926
+ // Database setup is optional, don't fail hard
927
+ completeTask('database');
928
+ await sleep(layout.taskCompletionDelay);
929
+ }
930
+ completeStep('database');
931
+ await sleep(layout.stepTransitionDelay);
932
+ // ============================================
933
+ // PHASE 4: Configuration & Ready
934
+ // ============================================
935
+ activateStep('ready');
936
+ await sleep(layout.stepTransitionDelay);
937
+ // Create workspace directory
938
+ if (!existsSync(workspace)) {
939
+ await mkdir(workspace, { recursive: true });
940
+ }
941
+ // Save configuration
942
+ startTask('config', 'Writing configuration...');
943
+ try {
944
+ configManager.set('workspace', workspace);
945
+ if (monorepoPath) {
946
+ configManager.set('monorepoPath', monorepoPath);
947
+ }
948
+ if (databaseUrl) {
949
+ configManager.set('databaseUrl', databaseUrl);
950
+ }
951
+ configManager.set('apiUrl', apiUrl);
952
+ const wsProtocol = apiUrl.startsWith('https://') ? 'wss://' : 'ws://';
953
+ const hostPath = apiUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
954
+ const wsUrl = `${wsProtocol}${hostPath}/ws/runner`;
955
+ configManager.set('server', {
956
+ wsUrl: wsUrl,
957
+ secret: generatedSecret,
958
+ });
959
+ configManager.set('runner', {
960
+ id: 'local',
961
+ reconnectAttempts: 5,
962
+ heartbeatInterval: 15000,
963
+ });
964
+ configManager.set('tunnel', {
965
+ provider: 'cloudflare',
966
+ autoCreate: true,
967
+ });
968
+ // Create .env.local
969
+ if (monorepoPath) {
970
+ const envLocalPath = join(monorepoPath, 'apps', 'openbuilder', '.env.local');
971
+ const envContent = [
972
+ '# Auto-generated by openbuilder CLI',
973
+ `# Generated at: ${new Date().toISOString()}`,
974
+ '',
975
+ 'OPENBUILDER_LOCAL_MODE=true',
976
+ `RUNNER_SHARED_SECRET=${generatedSecret}`,
977
+ `WORKSPACE_ROOT=${workspace}`,
978
+ 'RUNNER_ID=local',
979
+ 'RUNNER_DEFAULT_ID=local',
980
+ `DATABASE_URL=${databaseUrl || ''}`,
981
+ '',
982
+ ].join('\n');
983
+ await writeFile(envLocalPath, envContent);
984
+ }
985
+ completeTask('config');
986
+ await sleep(layout.taskCompletionDelay);
987
+ }
988
+ catch (error) {
989
+ failTask('config', 'Config save failed');
990
+ failStep('ready');
991
+ setError('Failed to save configuration', [
992
+ 'Check file permissions',
993
+ 'Try running from a different directory',
994
+ ]);
995
+ throw error;
996
+ }
997
+ // Build all services for production
998
+ if (monorepoPath) {
999
+ startTask('services', 'Building services (this may take a minute)...');
1000
+ // Track build output for error reporting (capture both stdout and stderr)
1001
+ let buildOutput = '';
1002
+ let buildError = '';
1003
+ try {
1004
+ const { spawn } = await import('child_process');
1005
+ await new Promise((resolve, reject) => {
1006
+ const buildProcess = spawn('pnpm', ['build:all'], {
1007
+ cwd: monorepoPath,
1008
+ stdio: 'pipe',
1009
+ shell: true,
1010
+ });
1011
+ // Capture stdout - many build tools output errors here
1012
+ buildProcess.stdout?.on('data', (data) => {
1013
+ buildOutput += data.toString();
1014
+ });
1015
+ // Capture stderr
1016
+ buildProcess.stderr?.on('data', (data) => {
1017
+ buildError += data.toString();
1018
+ });
1019
+ buildProcess.on('close', (code) => {
1020
+ if (code === 0)
1021
+ resolve();
1022
+ else
1023
+ reject(new Error(`Build failed with code ${code}`));
1024
+ });
1025
+ buildProcess.on('error', reject);
1026
+ });
1027
+ completeTask('services');
1028
+ await sleep(layout.taskCompletionDelay);
1029
+ }
1030
+ catch (error) {
1031
+ // Build failed - surface the error to the user
1032
+ failTask('services', 'Build failed');
1033
+ failStep('ready');
1034
+ // Combine all output for analysis
1035
+ const allOutput = (buildOutput + '\n' + buildError).trim();
1036
+ const allLines = allOutput.split('\n');
1037
+ // Find the most relevant error lines with better patterns
1038
+ const errorPatterns = [
1039
+ /error TS\d+:/i, // TypeScript errors
1040
+ /error:/i, // General errors
1041
+ /Error:/, // Error messages
1042
+ /ERR!/, // npm/pnpm errors
1043
+ /failed/i, // Failed messages
1044
+ /Cannot find/i, // Module not found
1045
+ /Module not found/i, // Webpack/Next.js errors
1046
+ /SyntaxError/i, // Syntax errors
1047
+ /TypeError/i, // Type errors
1048
+ /ReferenceError/i, // Reference errors
1049
+ /ENOENT/i, // File not found
1050
+ /✖|✗|×/, // Error symbols
1051
+ ];
1052
+ // Find lines that match error patterns
1053
+ const relevantLines = [];
1054
+ let inErrorBlock = false;
1055
+ for (let i = 0; i < allLines.length; i++) {
1056
+ const line = allLines[i];
1057
+ const isErrorLine = errorPatterns.some(pattern => pattern.test(line));
1058
+ if (isErrorLine) {
1059
+ inErrorBlock = true;
1060
+ // Include 1 line before for context if available
1061
+ if (i > 0 && relevantLines.length === 0) {
1062
+ const prevLine = allLines[i - 1].trim();
1063
+ if (prevLine && !prevLine.startsWith('>')) {
1064
+ relevantLines.push(prevLine);
1065
+ }
1066
+ }
1067
+ }
1068
+ if (inErrorBlock) {
1069
+ relevantLines.push(line.trim());
1070
+ // Collect more lines for scrollable view (max 50 lines)
1071
+ if (relevantLines.length >= 50)
1072
+ break;
1073
+ }
1074
+ // End error block on empty line or success indicators
1075
+ if (inErrorBlock && (line.trim() === '' || /successfully|completed/i.test(line))) {
1076
+ // Keep going in case there are more errors
1077
+ inErrorBlock = false;
1078
+ }
1079
+ }
1080
+ // If no specific errors found, show last 30 lines of output
1081
+ const displayLines = relevantLines.length > 0
1082
+ ? relevantLines
1083
+ : allLines.slice(-30);
1084
+ const suggestions = [
1085
+ 'To debug further, run manually:',
1086
+ ` cd ${monorepoPath} && pnpm build:all`,
1087
+ '',
1088
+ 'Common fixes:',
1089
+ ' - Run: pnpm install (missing dependencies)',
1090
+ ' - Check for TypeScript errors in the files mentioned above',
1091
+ ' - Ensure all environment variables are set',
1092
+ ];
1093
+ // Use setBuildError to show full-screen scrollable error view
1094
+ setBuildError('Production build failed', displayLines, suggestions);
1095
+ throw new CLIError({
1096
+ code: 'BUILD_FAILED',
1097
+ message: 'Failed to build services for production',
1098
+ suggestions: [
1099
+ `Run manually: cd ${monorepoPath} && pnpm build:all`,
1100
+ 'Check for TypeScript errors in the output above',
1101
+ 'Ensure all dependencies are installed: pnpm install',
1102
+ ],
1103
+ });
1104
+ }
1105
+ }
1106
+ // Validate configuration
1107
+ const validation = configManager.validate();
1108
+ if (!validation.valid) {
1109
+ failStep('ready');
1110
+ setError('Configuration invalid', validation.errors);
1111
+ throw new CLIError({
1112
+ code: 'CONFIG_INVALID',
1113
+ message: 'Configuration validation failed',
1114
+ });
1115
+ }
1116
+ completeStep('ready');
1117
+ return {
1118
+ workspace,
1119
+ monorepoPath,
1120
+ databaseUrl,
1121
+ apiUrl,
1122
+ runnerId: 'local',
1123
+ };
1124
+ }
1125
+
1126
+ export { initTUICommand };
1127
+ //# sourceMappingURL=init-tui-BNzk_7Yx.js.map