@agentuity/cli 0.0.72 → 0.0.74

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 (126) hide show
  1. package/bin/cli.ts +19 -5
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +13 -9
  4. package/dist/auth.js.map +1 -1
  5. package/dist/banner.js +1 -1
  6. package/dist/banner.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +79 -21
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cmd/ai/prompt/api.d.ts.map +1 -1
  11. package/dist/cmd/ai/prompt/api.js +5 -4
  12. package/dist/cmd/ai/prompt/api.js.map +1 -1
  13. package/dist/cmd/auth/api.d.ts +2 -2
  14. package/dist/cmd/auth/api.d.ts.map +1 -1
  15. package/dist/cmd/auth/api.js +15 -14
  16. package/dist/cmd/auth/api.js.map +1 -1
  17. package/dist/cmd/auth/login.d.ts.map +1 -1
  18. package/dist/cmd/auth/login.js +37 -16
  19. package/dist/cmd/auth/login.js.map +1 -1
  20. package/dist/cmd/auth/ssh/api.d.ts.map +1 -1
  21. package/dist/cmd/auth/ssh/api.js +3 -2
  22. package/dist/cmd/auth/ssh/api.js.map +1 -1
  23. package/dist/cmd/build/ast.d.ts.map +1 -1
  24. package/dist/cmd/build/ast.js +76 -14
  25. package/dist/cmd/build/ast.js.map +1 -1
  26. package/dist/cmd/build/bundler.d.ts +3 -1
  27. package/dist/cmd/build/bundler.d.ts.map +1 -1
  28. package/dist/cmd/build/bundler.js +21 -9
  29. package/dist/cmd/build/bundler.js.map +1 -1
  30. package/dist/cmd/build/format-schema.d.ts +6 -0
  31. package/dist/cmd/build/format-schema.d.ts.map +1 -0
  32. package/dist/cmd/build/format-schema.js +60 -0
  33. package/dist/cmd/build/format-schema.js.map +1 -0
  34. package/dist/cmd/build/index.d.ts.map +1 -1
  35. package/dist/cmd/build/index.js +13 -0
  36. package/dist/cmd/build/index.js.map +1 -1
  37. package/dist/cmd/build/plugin.d.ts.map +1 -1
  38. package/dist/cmd/build/plugin.js +123 -32
  39. package/dist/cmd/build/plugin.js.map +1 -1
  40. package/dist/cmd/build/route-discovery.d.ts +50 -0
  41. package/dist/cmd/build/route-discovery.d.ts.map +1 -0
  42. package/dist/cmd/build/route-discovery.js +143 -0
  43. package/dist/cmd/build/route-discovery.js.map +1 -0
  44. package/dist/cmd/build/route-registry.d.ts.map +1 -1
  45. package/dist/cmd/build/route-registry.js +25 -10
  46. package/dist/cmd/build/route-registry.js.map +1 -1
  47. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  48. package/dist/cmd/cloud/deploy.js +8 -6
  49. package/dist/cmd/cloud/deploy.js.map +1 -1
  50. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  51. package/dist/cmd/cloud/deployment/show.js +34 -10
  52. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  53. package/dist/cmd/dev/agents.d.ts.map +1 -1
  54. package/dist/cmd/dev/agents.js +2 -2
  55. package/dist/cmd/dev/agents.js.map +1 -1
  56. package/dist/cmd/dev/index.d.ts.map +1 -1
  57. package/dist/cmd/dev/index.js +21 -0
  58. package/dist/cmd/dev/index.js.map +1 -1
  59. package/dist/cmd/dev/sync.d.ts.map +1 -1
  60. package/dist/cmd/dev/sync.js +2 -2
  61. package/dist/cmd/dev/sync.js.map +1 -1
  62. package/dist/cmd/project/download.d.ts.map +1 -1
  63. package/dist/cmd/project/download.js +16 -2
  64. package/dist/cmd/project/download.js.map +1 -1
  65. package/dist/cmd/project/list.d.ts.map +1 -1
  66. package/dist/cmd/project/list.js +2 -10
  67. package/dist/cmd/project/list.js.map +1 -1
  68. package/dist/cmd/project/show.d.ts.map +1 -1
  69. package/dist/cmd/project/show.js +8 -7
  70. package/dist/cmd/project/show.js.map +1 -1
  71. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  72. package/dist/cmd/project/template-flow.js +14 -2
  73. package/dist/cmd/project/template-flow.js.map +1 -1
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/config.js +9 -0
  76. package/dist/config.js.map +1 -1
  77. package/dist/index.d.ts +2 -2
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/steps.d.ts +20 -30
  82. package/dist/steps.d.ts.map +1 -1
  83. package/dist/steps.js +339 -184
  84. package/dist/steps.js.map +1 -1
  85. package/dist/tui/box.d.ts.map +1 -1
  86. package/dist/tui/box.js +8 -4
  87. package/dist/tui/box.js.map +1 -1
  88. package/dist/tui/prompt.d.ts.map +1 -1
  89. package/dist/tui/prompt.js +7 -2
  90. package/dist/tui/prompt.js.map +1 -1
  91. package/dist/tui.d.ts +20 -1
  92. package/dist/tui.d.ts.map +1 -1
  93. package/dist/tui.js +90 -18
  94. package/dist/tui.js.map +1 -1
  95. package/dist/types.d.ts +10 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/package.json +3 -3
  98. package/src/auth.ts +13 -10
  99. package/src/banner.ts +1 -1
  100. package/src/cli.ts +89 -27
  101. package/src/cmd/ai/prompt/api.ts +5 -4
  102. package/src/cmd/auth/api.ts +20 -22
  103. package/src/cmd/auth/login.ts +36 -17
  104. package/src/cmd/auth/ssh/api.ts +5 -9
  105. package/src/cmd/build/ast.ts +88 -14
  106. package/src/cmd/build/bundler.ts +32 -11
  107. package/src/cmd/build/format-schema.ts +66 -0
  108. package/src/cmd/build/index.ts +14 -0
  109. package/src/cmd/build/plugin.ts +146 -36
  110. package/src/cmd/build/route-discovery.ts +197 -0
  111. package/src/cmd/build/route-registry.ts +26 -10
  112. package/src/cmd/cloud/deploy.ts +19 -6
  113. package/src/cmd/cloud/deployment/show.ts +42 -10
  114. package/src/cmd/dev/agents.ts +2 -10
  115. package/src/cmd/dev/index.ts +25 -0
  116. package/src/cmd/dev/sync.ts +2 -12
  117. package/src/cmd/project/download.ts +16 -2
  118. package/src/cmd/project/list.ts +2 -9
  119. package/src/cmd/project/show.ts +8 -6
  120. package/src/cmd/project/template-flow.ts +21 -2
  121. package/src/config.ts +10 -0
  122. package/src/index.ts +2 -2
  123. package/src/steps.ts +397 -229
  124. package/src/tui/box.ts +8 -4
  125. package/src/tui/prompt.ts +7 -4
  126. package/src/tui.ts +125 -20
package/dist/steps.js CHANGED
@@ -1,61 +1,10 @@
1
1
  /**
2
- * Step progress UI component for showing animated steps with callbacks
2
+ * Steps UI Component v2 - Clean state-driven implementation
3
3
  *
4
- * Displays a checklist where each step animates in place with a spinner,
5
- * then shows success, skipped, or error icon based on callback result.
4
+ * Key principle: Render entire step list from state on every update cycle.
5
+ * Track total lines rendered to calculate cursor movement.
6
6
  */
7
7
  import { ValidationInputError, ValidationOutputError } from '@agentuity/server';
8
- /**
9
- * Get the appropriate exit function (Bun.exit or process.exit)
10
- */
11
- function getExitFn() {
12
- const bunExit = globalThis.Bun?.exit;
13
- return typeof bunExit === 'function' ? bunExit : process.exit.bind(process);
14
- }
15
- /**
16
- * Install interrupt handlers (SIGINT/SIGTERM + TTY raw mode for Ctrl+C)
17
- */
18
- function installInterruptHandlers(onInterrupt) {
19
- const cleanupFns = [];
20
- const sigHandler = () => onInterrupt();
21
- process.on('SIGINT', sigHandler);
22
- process.on('SIGTERM', sigHandler);
23
- cleanupFns.push(() => {
24
- process.off('SIGINT', sigHandler);
25
- process.off('SIGTERM', sigHandler);
26
- });
27
- // TTY raw mode fallback for Bun/Windows/inconsistent SIGINT delivery
28
- const stdin = process.stdin;
29
- if (stdin && stdin.isTTY) {
30
- const onData = (buf) => {
31
- // Ctrl+C is ASCII ETX (0x03)
32
- if (buf.length === 1 && buf[0] === 0x03)
33
- onInterrupt();
34
- };
35
- try {
36
- stdin.setRawMode?.(true);
37
- }
38
- catch {
39
- // ignore if not supported
40
- }
41
- stdin.resume?.();
42
- stdin.on('data', onData);
43
- cleanupFns.push(() => {
44
- stdin.off?.('data', onData);
45
- stdin.pause?.();
46
- try {
47
- stdin.setRawMode?.(false);
48
- }
49
- catch {
50
- // ignore if setRawMode fails
51
- }
52
- });
53
- }
54
- return () => {
55
- for (const fn of cleanupFns.splice(0))
56
- fn();
57
- };
58
- }
59
8
  // Spinner frames
60
9
  const FRAMES = ['◐', '◓', '◑', '◒'];
61
10
  // Icons
@@ -65,7 +14,7 @@ const ICONS = {
65
14
  error: '✗',
66
15
  pending: '☐',
67
16
  };
68
- // Color definitions (light/dark adaptive)
17
+ // Color definitions
69
18
  const COLORS = {
70
19
  cyan: { light: '\x1b[36m', dark: '\x1b[96m' },
71
20
  blue: { light: '\x1b[34m', dark: '\x1b[94m' },
@@ -78,12 +27,8 @@ const COLORS = {
78
27
  strikethrough: '\x1b[9m',
79
28
  reset: '\x1b[0m',
80
29
  };
81
- // Spinner color sequence
82
30
  const SPINNER_COLORS = ['cyan', 'blue', 'magenta', 'cyan'];
83
- let currentColorScheme = process.env.CI ? 'light' : 'dark';
84
- export function setStepsColorScheme(scheme) {
85
- currentColorScheme = scheme;
86
- }
31
+ const currentColorScheme = process.env.CI ? 'light' : 'dark';
87
32
  function getColor(colorKey) {
88
33
  const color = COLORS[colorKey];
89
34
  if (typeof color === 'string') {
@@ -94,42 +39,80 @@ function getColor(colorKey) {
94
39
  /**
95
40
  * Helper functions for creating step outcomes
96
41
  */
97
- export const stepSuccess = () => ({ status: 'success' });
98
- export const stepSkipped = (reason) => ({ status: 'skipped', reason });
99
- export const stepError = (message, cause) => ({
42
+ export const stepSuccess = (output) => ({ status: 'success', output });
43
+ export const stepSkipped = (reason, output) => ({
44
+ status: 'skipped',
45
+ reason,
46
+ output,
47
+ });
48
+ export const stepError = (message, cause, output) => ({
100
49
  status: 'error',
101
50
  message,
102
51
  cause,
52
+ output,
103
53
  });
104
54
  /**
105
- * Run a series of steps with animated progress
106
- *
107
- * Each step runs its callback while showing a spinner animation.
108
- * Steps can complete with success, skipped, or error status.
109
- * Exits with code 1 if any step errors.
110
- *
111
- * When there's no TTY or log level is debug/trace, uses plain output instead of TUI.
55
+ * Render a single step line (without output box)
112
56
  */
113
- export async function runSteps(steps, logLevel) {
114
- const state = steps.map((s) => {
115
- const stepType = s.type === 'progress' ? 'progress' : 'simple';
116
- return stepType === 'progress'
117
- ? {
118
- type: 'progress',
119
- label: s.label,
120
- run: s.run,
121
- }
122
- : { type: 'simple', label: s.label, run: s.run };
123
- });
124
- // Detect if we should use TUI (animated) or plain mode
125
- const useTUI = process.stdout.isTTY && (!logLevel || ['info', 'warn', 'error'].includes(logLevel));
126
- if (useTUI) {
127
- await runStepsTUI(state);
57
+ function renderStepLine(step, spinner) {
58
+ const grayColor = getColor('gray');
59
+ const greenColor = getColor('green');
60
+ const yellowColor = getColor('yellow');
61
+ const redColor = getColor('red');
62
+ const cyanColor = getColor('cyan');
63
+ if (step.status === 'success') {
64
+ return `${greenColor}${ICONS.success}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${step.label}${COLORS.reset}`;
65
+ }
66
+ else if (step.status === 'skipped') {
67
+ const reason = step.skipReason ? ` ${grayColor}(${step.skipReason})${COLORS.reset}` : '';
68
+ return `${yellowColor}${ICONS.skipped}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${step.label}${COLORS.reset}${reason}`;
69
+ }
70
+ else if (step.status === 'error') {
71
+ return `${redColor}${ICONS.error}${COLORS.reset} ${step.label}`;
72
+ }
73
+ else if (step.status === 'running' && spinner) {
74
+ const progressIndicator = step.progress !== undefined
75
+ ? ` ${cyanColor}${Math.floor(step.progress)}%${COLORS.reset}`
76
+ : '';
77
+ return `${spinner} ${step.label}${progressIndicator}`;
128
78
  }
129
79
  else {
130
- await runStepsPlain(state);
80
+ return `${grayColor}${ICONS.pending}${COLORS.reset} ${step.label}`;
81
+ }
82
+ }
83
+ /**
84
+ * Render all steps from state, including output boxes
85
+ * Returns the rendered output and total line count
86
+ */
87
+ function renderAllSteps(state, runningStepIndex, spinner) {
88
+ const lines = [];
89
+ let totalLines = 0;
90
+ const grayColor = getColor('gray');
91
+ for (let i = 0; i < state.length; i++) {
92
+ const step = state[i];
93
+ const isRunning = i === runningStepIndex;
94
+ const stepSpinner = isRunning && spinner ? spinner : undefined;
95
+ // Render step line
96
+ lines.push(renderStepLine(step, stepSpinner));
97
+ totalLines++;
98
+ // Render output box if present
99
+ if (step.output && step.output.length > 0) {
100
+ lines.push(`${grayColor}╭─ Output${COLORS.reset}`);
101
+ totalLines++;
102
+ for (const line of step.output) {
103
+ lines.push(`${grayColor}│${COLORS.reset} ${line}`);
104
+ totalLines++;
105
+ }
106
+ lines.push(`${grayColor}╰─${COLORS.reset}`);
107
+ totalLines++;
108
+ // Don't add blank line here - the '\n' we append during write creates separation
109
+ }
131
110
  }
111
+ return { output: lines.join('\n'), totalLines };
132
112
  }
113
+ /**
114
+ * Print validation issues (for ValidationInputError/ValidationOutputError)
115
+ */
133
116
  function printValidationIssues(issues) {
134
117
  const errorColor = getColor('red');
135
118
  console.error(`${errorColor}Validation details:${COLORS.reset}`);
@@ -141,15 +124,127 @@ function printValidationIssues(issues) {
141
124
  }
142
125
  }
143
126
  /**
144
- * Run steps with animated TUI (original behavior)
127
+ * Global pause state and tracking
128
+ */
129
+ let isPaused = false;
130
+ let getTotalLinesFn = null;
131
+ let forceRerenderFn = null;
132
+ export function isStepUIPaused() {
133
+ return isPaused;
134
+ }
135
+ /**
136
+ * Internal function to set pause capability
137
+ */
138
+ function enablePauseResume(getTotalLines, forceRerender) {
139
+ getTotalLinesFn = getTotalLines;
140
+ forceRerenderFn = forceRerender;
141
+ }
142
+ /**
143
+ * Pause step rendering for interactive input
144
+ * Returns resume function
145
+ */
146
+ export function pauseStepUI() {
147
+ if (!process.stdout.isTTY || !getTotalLinesFn) {
148
+ return () => { }; // No-op if not TTY or not in step context
149
+ }
150
+ isPaused = true;
151
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
152
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
153
+ // Intercept writes during pause (unused but prevents issues with interactive prompts)
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ process.stdout.write = ((chunk, ..._args) => {
156
+ return originalStdoutWrite(chunk);
157
+ });
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ process.stderr.write = ((chunk, ..._args) => {
160
+ return originalStderrWrite(chunk);
161
+ });
162
+ // Show cursor and add newline for separation
163
+ process.stdout.write('\x1B[?25h');
164
+ process.stdout.write('\n');
165
+ // Return resume function
166
+ return () => {
167
+ isPaused = false;
168
+ // Restore original write functions
169
+ process.stdout.write = originalStdoutWrite;
170
+ process.stderr.write = originalStderrWrite;
171
+ // Restore cursor to saved position (where steps began)
172
+ process.stdout.write('\x1B[u'); // Restore cursor position
173
+ process.stdout.write('\x1B[0J'); // Clear from saved position to end of screen
174
+ process.stdout.write('\x1B[?25l'); // Hide cursor
175
+ // Force immediate re-render (cursor already at step start)
176
+ if (forceRerenderFn) {
177
+ forceRerenderFn(true);
178
+ }
179
+ };
180
+ }
181
+ /**
182
+ * Get exit function (Bun.exit or process.exit)
145
183
  */
146
- async function runStepsTUI(state) {
184
+ function getExitFn() {
185
+ const bunExit = globalThis.Bun?.exit;
186
+ return typeof bunExit === 'function' ? bunExit : process.exit.bind(process);
187
+ }
188
+ /**
189
+ * Install interrupt handlers (SIGINT/SIGTERM + raw mode)
190
+ */
191
+ function installInterruptHandlers(onInterrupt) {
192
+ const cleanupFns = [];
193
+ const sigHandler = () => onInterrupt();
194
+ process.on('SIGINT', sigHandler);
195
+ process.on('SIGTERM', sigHandler);
196
+ cleanupFns.push(() => {
197
+ process.off('SIGINT', sigHandler);
198
+ process.off('SIGTERM', sigHandler);
199
+ });
200
+ // TTY raw mode fallback
201
+ const stdin = process.stdin;
202
+ if (stdin && stdin.isTTY) {
203
+ const onData = (buf) => {
204
+ if (buf.length === 1 && buf[0] === 0x03)
205
+ onInterrupt();
206
+ };
207
+ try {
208
+ stdin.setRawMode?.(true);
209
+ }
210
+ catch {
211
+ // Ignore errors
212
+ }
213
+ stdin.resume?.();
214
+ stdin.on('data', onData);
215
+ cleanupFns.push(() => {
216
+ stdin.off?.('data', onData);
217
+ stdin.pause?.();
218
+ try {
219
+ stdin.setRawMode?.(false);
220
+ }
221
+ catch {
222
+ // Ignore errors
223
+ }
224
+ });
225
+ }
226
+ return () => {
227
+ for (const fn of cleanupFns.splice(0))
228
+ fn();
229
+ };
230
+ }
231
+ /**
232
+ * Run steps with TUI (animated mode)
233
+ */
234
+ async function runStepsTUI(steps) {
235
+ // Initialize state
236
+ const state = steps.map((s) => ({
237
+ label: s.label,
238
+ status: 'pending',
239
+ }));
240
+ let totalLinesFromLastRender = 0;
241
+ let interrupted = false;
242
+ let activeInterval = null;
243
+ let currentStepIndex = -1;
244
+ let currentFrameIndex = 0;
147
245
  // Hide cursor
148
246
  process.stdout.write('\x1B[?25l');
149
- // Track active interval and interrupted state
150
- let activeInterval = null;
151
- let interrupted = false;
152
- // Set up Ctrl+C handler for graceful exit
247
+ // Set up interrupt handler
153
248
  const exit = getExitFn();
154
249
  const onInterrupt = () => {
155
250
  if (interrupted)
@@ -161,116 +256,204 @@ async function runStepsTUI(state) {
161
256
  exit(130);
162
257
  };
163
258
  const restoreInterrupts = installInterruptHandlers(onInterrupt);
259
+ // Force re-render function
260
+ const forceRerender = (skipMove = false) => {
261
+ if (currentStepIndex < 0 || currentStepIndex >= state.length)
262
+ return;
263
+ const colorKey = SPINNER_COLORS[currentFrameIndex % SPINNER_COLORS.length];
264
+ const color = getColor(colorKey);
265
+ const spinner = `${color}${COLORS.bold}${FRAMES[currentFrameIndex % FRAMES.length]}${COLORS.reset}`;
266
+ const rendered = renderAllSteps(state, currentStepIndex, spinner);
267
+ // Optionally move up, then to column 0
268
+ if (!skipMove && totalLinesFromLastRender > 0) {
269
+ process.stdout.write(`\x1B[${totalLinesFromLastRender}A`);
270
+ process.stdout.write('\x1B[0G');
271
+ }
272
+ process.stdout.write('\x1B[0J');
273
+ process.stdout.write(rendered.output + '\n');
274
+ totalLinesFromLastRender = rendered.totalLines;
275
+ };
164
276
  try {
277
+ // Enable pause/resume capability
278
+ enablePauseResume(() => totalLinesFromLastRender, forceRerender);
279
+ // Save cursor position BEFORE rendering steps
280
+ process.stdout.write('\x1B[s');
165
281
  // Initial render
166
- process.stdout.write(renderSteps(state, -1) + '\n');
167
- for (let stepIndex = 0; stepIndex < state.length; stepIndex++) {
282
+ const initialRender = renderAllSteps(state, -1);
283
+ process.stdout.write(initialRender.output + '\n');
284
+ totalLinesFromLastRender = initialRender.totalLines;
285
+ // Execute steps
286
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
168
287
  if (interrupted)
169
288
  break;
170
- const step = state[stepIndex];
171
- let frameIndex = 0;
172
- let currentFrame = '';
289
+ currentStepIndex = stepIndex;
290
+ currentFrameIndex = 0;
291
+ const step = steps[stepIndex];
292
+ const stepState = state[stepIndex];
293
+ stepState.status = 'running';
173
294
  // Start spinner animation
174
295
  activeInterval = setInterval(() => {
175
- const colorKey = SPINNER_COLORS[frameIndex % SPINNER_COLORS.length];
296
+ if (isPaused)
297
+ return;
298
+ const colorKey = SPINNER_COLORS[currentFrameIndex % SPINNER_COLORS.length];
176
299
  const color = getColor(colorKey);
177
- currentFrame = `${color}${COLORS.bold}${FRAMES[frameIndex % FRAMES.length]}${COLORS.reset}`;
178
- // Move cursor up to the top of checklist
179
- process.stdout.write(`\x1B[${state.length}A`);
180
- process.stdout.write(renderSteps(state, stepIndex, currentFrame) + '\n');
181
- frameIndex++;
300
+ const spinner = `${color}${COLORS.bold}${FRAMES[currentFrameIndex % FRAMES.length]}${COLORS.reset}`;
301
+ // Render all steps from state
302
+ const rendered = renderAllSteps(state, currentStepIndex, spinner);
303
+ // Move to start, clear, render
304
+ if (totalLinesFromLastRender > 0) {
305
+ process.stdout.write(`\x1B[${totalLinesFromLastRender}A`); // Move up
306
+ process.stdout.write('\x1B[0G'); // Move to column 0 (using absolute positioning)
307
+ }
308
+ process.stdout.write('\x1B[0J'); // Clear from cursor to end
309
+ process.stdout.write(rendered.output + '\n');
310
+ totalLinesFromLastRender = rendered.totalLines;
311
+ currentFrameIndex++;
182
312
  }, 120);
183
- // Run the step with progress tracking
313
+ // Progress callback
184
314
  const progressCallback = (progress) => {
185
- step.progress = Math.min(100, Math.max(0, progress));
186
- // Move cursor up and render with current spinner frame
187
- process.stdout.write(`\x1B[${state.length}A`);
188
- process.stdout.write(renderSteps(state, stepIndex, currentFrame) + '\n');
315
+ if (isPaused)
316
+ return;
317
+ stepState.progress = Math.min(100, Math.max(0, progress));
318
+ // Render all steps from state
319
+ const colorKey = SPINNER_COLORS[currentFrameIndex % SPINNER_COLORS.length];
320
+ const color = getColor(colorKey);
321
+ const spinner = `${color}${COLORS.bold}${FRAMES[currentFrameIndex % FRAMES.length]}${COLORS.reset}`;
322
+ const rendered = renderAllSteps(state, currentStepIndex, spinner);
323
+ // Move to start, clear, render
324
+ if (totalLinesFromLastRender > 0) {
325
+ process.stdout.write(`\x1B[${totalLinesFromLastRender}A`);
326
+ process.stdout.write('\x1B[0G');
327
+ }
328
+ process.stdout.write('\x1B[0J');
329
+ process.stdout.write(rendered.output + '\n');
330
+ totalLinesFromLastRender = rendered.totalLines;
189
331
  };
332
+ // Run the step
190
333
  try {
191
- // Use discriminant to determine if step has progress callback
192
- const outcome = step.type === 'progress' ? await step.run(progressCallback) : await step.run();
193
- step.outcome = outcome;
334
+ const outcome = await step.run({ progress: progressCallback });
335
+ // Update state from outcome
336
+ if (outcome.status === 'success') {
337
+ stepState.status = 'success';
338
+ stepState.output = outcome.output;
339
+ }
340
+ else if (outcome.status === 'skipped') {
341
+ stepState.status = 'skipped';
342
+ stepState.skipReason = outcome.reason;
343
+ stepState.output = outcome.output;
344
+ }
345
+ else {
346
+ stepState.status = 'error';
347
+ stepState.errorMessage = outcome.message;
348
+ stepState.errorCause = outcome.cause;
349
+ stepState.output = outcome.output;
350
+ }
194
351
  }
195
352
  catch (err) {
196
- step.outcome = {
197
- status: 'error',
198
- message: err instanceof Error ? err.message : String(err),
199
- cause: err instanceof Error ? err : undefined,
200
- };
353
+ stepState.status = 'error';
354
+ stepState.errorMessage = err instanceof Error ? err.message : String(err);
355
+ stepState.errorCause = err instanceof Error ? err : undefined;
201
356
  }
357
+ // Stop spinner
202
358
  if (activeInterval) {
203
359
  clearInterval(activeInterval);
204
360
  activeInterval = null;
205
361
  }
206
- // Clear progress and final render with outcome
207
- step.progress = undefined;
208
- process.stdout.write(`\x1B[${state.length}A`);
209
- process.stdout.write(renderSteps(state, -1) + '\n');
210
- // If error, show error message and exit
211
- if (step.outcome?.status === 'error') {
362
+ // Final render with outcome
363
+ stepState.progress = undefined;
364
+ const finalRender = renderAllSteps(state, -1);
365
+ if (totalLinesFromLastRender > 0) {
366
+ process.stdout.write(`\x1B[${totalLinesFromLastRender}A`);
367
+ process.stdout.write('\x1B[0G');
368
+ }
369
+ process.stdout.write('\x1B[0J');
370
+ process.stdout.write(finalRender.output + '\n');
371
+ totalLinesFromLastRender = finalRender.totalLines;
372
+ // Handle errors
373
+ if (stepState.status === 'error') {
212
374
  const errorColor = getColor('red');
213
- console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}`);
214
- if (step.outcome.cause instanceof ValidationInputError ||
215
- step.outcome.cause instanceof ValidationOutputError) {
216
- printValidationIssues(step.outcome.cause.issues);
375
+ console.error(`\n${errorColor}Error: ${stepState.errorMessage}${COLORS.reset}`);
376
+ if (stepState.errorCause instanceof ValidationInputError ||
377
+ stepState.errorCause instanceof ValidationOutputError) {
378
+ printValidationIssues(stepState.errorCause.issues);
217
379
  }
218
380
  console.error('');
219
381
  process.stdout.write('\x1B[?25h'); // Show cursor
220
382
  process.exit(1);
221
383
  }
222
384
  }
223
- // Show cursor again
385
+ // Show cursor
224
386
  process.stdout.write('\x1B[?25h');
225
387
  }
226
388
  catch (err) {
227
- // Ensure cursor is shown even if something goes wrong
228
389
  process.stdout.write('\x1B[?25h');
229
390
  throw err;
230
391
  }
231
392
  finally {
232
- // Remove signal/TTY handlers
233
393
  restoreInterrupts();
394
+ getTotalLinesFn = null; // Clear pause capability
395
+ forceRerenderFn = null;
234
396
  }
235
397
  }
236
398
  /**
237
- * Run steps in plain mode (no TUI animations)
399
+ * Run steps in plain mode (no animations)
238
400
  */
239
- async function runStepsPlain(state) {
401
+ async function runStepsPlain(steps) {
240
402
  const grayColor = getColor('gray');
241
403
  const greenColor = getColor('green');
242
404
  const yellowColor = getColor('yellow');
243
405
  const redColor = getColor('red');
244
- for (const step of state) {
245
- // Run the step (no progress callback for plain mode)
406
+ for (const step of steps) {
407
+ let outcome;
246
408
  try {
247
- const outcome = step.type === 'progress' ? await step.run(() => { }) : await step.run();
248
- step.outcome = outcome;
409
+ outcome = await step.run({ progress: () => { } });
249
410
  }
250
411
  catch (err) {
251
- step.outcome = {
412
+ outcome = {
252
413
  status: 'error',
253
414
  message: err instanceof Error ? err.message : String(err),
254
415
  cause: err instanceof Error ? err : undefined,
255
416
  };
256
417
  }
257
- // Print final state only
258
- if (step.outcome?.status === 'success') {
418
+ // Print final state
419
+ if (outcome.status === 'success') {
259
420
  console.log(`${greenColor}${ICONS.success}${COLORS.reset} ${step.label}`);
421
+ if (outcome.output && outcome.output.length > 0) {
422
+ console.log(`${grayColor}╭─ Output${COLORS.reset}`);
423
+ for (const line of outcome.output) {
424
+ console.log(`${grayColor}│${COLORS.reset} ${line}`);
425
+ }
426
+ console.log(`${grayColor}╰─${COLORS.reset}`);
427
+ console.log('');
428
+ }
260
429
  }
261
- else if (step.outcome?.status === 'skipped') {
262
- const reason = step.outcome.reason
263
- ? ` ${grayColor}(${step.outcome.reason})${COLORS.reset}`
264
- : '';
430
+ else if (outcome.status === 'skipped') {
431
+ const reason = outcome.reason ? ` ${grayColor}(${outcome.reason})${COLORS.reset}` : '';
265
432
  console.log(`${yellowColor}${ICONS.skipped}${COLORS.reset} ${step.label}${reason}`);
433
+ if (outcome.output && outcome.output.length > 0) {
434
+ console.log(`${grayColor}╭─ Output${COLORS.reset}`);
435
+ for (const line of outcome.output) {
436
+ console.log(`${grayColor}│${COLORS.reset} ${line}`);
437
+ }
438
+ console.log(`${grayColor}╰─${COLORS.reset}`);
439
+ console.log('');
440
+ }
266
441
  }
267
- else if (step.outcome?.status === 'error') {
442
+ else {
268
443
  console.log(`${redColor}${ICONS.error}${COLORS.reset} ${step.label}`);
444
+ if (outcome.output && outcome.output.length > 0) {
445
+ console.log(`${grayColor}╭─ Output${COLORS.reset}`);
446
+ for (const line of outcome.output) {
447
+ console.log(`${grayColor}│${COLORS.reset} ${line}`);
448
+ }
449
+ console.log(`${grayColor}╰─${COLORS.reset}`);
450
+ console.log('');
451
+ }
269
452
  const errorColor = getColor('red');
270
- console.error(`\n${errorColor}Error: ${step.outcome.message}${COLORS.reset}`);
271
- if (step.outcome.cause instanceof ValidationInputError ||
272
- step.outcome.cause instanceof ValidationOutputError) {
273
- printValidationIssues(step.outcome.cause.issues);
453
+ console.error(`\n${errorColor}Error: ${outcome.message}${COLORS.reset}`);
454
+ if (outcome.cause instanceof ValidationInputError ||
455
+ outcome.cause instanceof ValidationOutputError) {
456
+ printValidationIssues(outcome.cause.issues);
274
457
  }
275
458
  console.error('');
276
459
  process.exit(1);
@@ -278,43 +461,15 @@ async function runStepsPlain(state) {
278
461
  }
279
462
  }
280
463
  /**
281
- * Render a progress indicator
282
- */
283
- function renderProgress(progress) {
284
- const cyanColor = getColor('cyan');
285
- const percentage = `${Math.floor(progress)}%`;
286
- return ` ${cyanColor}${percentage}${COLORS.reset}`;
287
- }
288
- /**
289
- * Render all steps as a multiline string
464
+ * Run a series of steps with animated progress
290
465
  */
291
- function renderSteps(steps, activeIndex, spinner) {
292
- const grayColor = getColor('gray');
293
- const greenColor = getColor('green');
294
- const yellowColor = getColor('yellow');
295
- const redColor = getColor('red');
296
- const lines = [];
297
- steps.forEach((s, i) => {
298
- // Don't show progress indicator for steps with outcomes (success/skipped/error)
299
- if (s.outcome?.status === 'success') {
300
- lines.push(`${greenColor}${ICONS.success}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${s.label}${COLORS.reset}`);
301
- }
302
- else if (s.outcome?.status === 'skipped') {
303
- const reason = s.outcome.reason ? ` ${grayColor}(${s.outcome.reason})${COLORS.reset}` : '';
304
- lines.push(`${yellowColor}${ICONS.skipped}${COLORS.reset} ${grayColor}${COLORS.strikethrough}${s.label}${COLORS.reset}${reason}`);
305
- }
306
- else if (s.outcome?.status === 'error') {
307
- lines.push(`${redColor}${ICONS.error}${COLORS.reset} ${s.label}`);
308
- }
309
- else if (i === activeIndex && spinner) {
310
- // Only show progress for active step with spinner
311
- const progressIndicator = s.progress !== undefined ? renderProgress(s.progress) : '';
312
- lines.push(`${spinner} ${s.label}${progressIndicator}`);
313
- }
314
- else {
315
- lines.push(`${grayColor}${ICONS.pending}${COLORS.reset} ${s.label}`);
316
- }
317
- });
318
- return lines.join('\n');
466
+ export async function runSteps(steps, logLevel) {
467
+ const useTUI = process.stdout.isTTY && (!logLevel || ['info', 'warn', 'error'].includes(logLevel));
468
+ if (useTUI) {
469
+ await runStepsTUI(steps);
470
+ }
471
+ else {
472
+ await runStepsPlain(steps);
473
+ }
319
474
  }
320
475
  //# sourceMappingURL=steps.js.map