boxdown 1.0.0 → 1.2.1

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 (76) hide show
  1. package/README.md +135 -12
  2. package/assets/devcontainer/README.md +65 -21
  3. package/assets/devcontainer/devcontainer.json +27 -15
  4. package/assets/devcontainer/hooks/initialize.sh +76 -22
  5. package/assets/devcontainer/hooks/post-create.sh +70 -12
  6. package/assets/devcontainer/hooks/post-start.sh +20 -13
  7. package/assets/devcontainer/ssh-config-install.sh +12 -3
  8. package/assets/devcontainer/start.sh +721 -44
  9. package/assets/devcontainer/utils/coding-agent-cli-update.sh +267 -3
  10. package/assets/devcontainer/utils/deps-install.sh +68 -0
  11. package/assets/devcontainer/utils/git-config-bootstrap.sh +128 -0
  12. package/assets/devcontainer/utils/git-signing-bootstrap.sh +109 -0
  13. package/assets/devcontainer/utils/python-bootstrap.sh +69 -0
  14. package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
  15. package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
  16. package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
  17. package/assets/devcontainer/utils/ssh-bootstrap.sh +9 -0
  18. package/dist/bin/cli.cjs +1 -1
  19. package/dist/bin/cli.mjs +1 -1
  20. package/dist/main-BDgyf2t5.cjs +5758 -0
  21. package/dist/main-J4_2Up3o.mjs +5718 -0
  22. package/dist/main-J4_2Up3o.mjs.map +1 -0
  23. package/dist/main.cjs +5 -2
  24. package/dist/main.d.cts +501 -4
  25. package/dist/main.d.cts.map +1 -1
  26. package/dist/main.d.mts +501 -4
  27. package/dist/main.d.mts.map +1 -1
  28. package/dist/main.mjs +2 -2
  29. package/docs/README.md +1 -0
  30. package/docs/architecture.md +32 -0
  31. package/docs/development.md +13 -6
  32. package/docs/features/README.md +2 -0
  33. package/docs/features/commit-signing.md +94 -0
  34. package/docs/features/generated-config-and-state.md +73 -5
  35. package/docs/features/github-auth-refresh.md +15 -2
  36. package/docs/features/lifecycle.md +103 -11
  37. package/docs/features/setup.md +66 -0
  38. package/docs/features/ssh-config-and-proxy.md +228 -7
  39. package/docs/features/start-and-shell.md +45 -5
  40. package/docs/superpowers/plans/2026-07-11-default-commit-signing.md +110 -0
  41. package/docs/superpowers/plans/2026-07-11-progress-ci-regression.md +125 -0
  42. package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
  43. package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
  44. package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +416 -0
  45. package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -0
  46. package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
  47. package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
  48. package/docs/testing.md +35 -2
  49. package/docs/todo.md +2 -2
  50. package/package.json +1 -1
  51. package/src/claude-app-config.ts +304 -0
  52. package/src/cli-style.ts +43 -0
  53. package/src/codex-app-config.ts +656 -0
  54. package/src/config.ts +80 -10
  55. package/src/constants.ts +12 -0
  56. package/src/devcontainer.ts +511 -64
  57. package/src/doctor.ts +292 -30
  58. package/src/git-signing.ts +267 -0
  59. package/src/interactive-prompts.ts +692 -0
  60. package/src/list.ts +16 -2
  61. package/src/logging.ts +164 -0
  62. package/src/main.ts +1214 -63
  63. package/src/metadata.ts +52 -3
  64. package/src/package-info.ts +18 -0
  65. package/src/paths.ts +71 -11
  66. package/src/process.ts +80 -2
  67. package/src/progress.ts +593 -0
  68. package/src/purge.ts +216 -0
  69. package/src/shell.ts +25 -0
  70. package/src/ssh-config.ts +134 -16
  71. package/src/ssh-install-targets.ts +111 -0
  72. package/src/ssh-key.ts +53 -13
  73. package/src/status.ts +5 -0
  74. package/dist/main-BuEptwlL.cjs +0 -1707
  75. package/dist/main-ZFTrSVgt.mjs +0 -1685
  76. package/dist/main-ZFTrSVgt.mjs.map +0 -1
@@ -0,0 +1,593 @@
1
+ import { runBuffered, type BufferedCommandOptions, type CommandResult } from './process.ts'
2
+ import { color, formatPromptEnd, formatPromptTitle, promptRail, selectedMark } from './cli-style.ts'
3
+
4
+ export type ProgressOutputTarget = 'stdout' | 'stderr'
5
+ export type ProgressMode = 'interactive' | 'verbose' | 'none'
6
+ export type ProgressWriter = (target: ProgressOutputTarget, message: string) => void
7
+ export type ProgressRawWriter = (target: ProgressOutputTarget, message: string) => void
8
+
9
+ export interface ProgressReporterOptions {
10
+ mode?: ProgressMode
11
+ verbose?: boolean
12
+ target?: ProgressOutputTarget
13
+ write?: ProgressWriter
14
+ writeRaw?: ProgressRawWriter
15
+ isTTY?: boolean
16
+ spinnerFrames?: readonly string[]
17
+ spinnerIntervalMs?: number
18
+ }
19
+
20
+ export interface ProgressCommandOptions extends Pick<BufferedCommandOptions, 'cwd' | 'env' | 'input'> {
21
+ logger?: BufferedCommandOptions['logger']
22
+ progress?: ProgressReporter
23
+ verboseStdout?: ProgressOutputTarget | false
24
+ verboseStderr?: ProgressOutputTarget | false
25
+ spinnerLabel?: string
26
+ stepId?: string
27
+ }
28
+
29
+ export interface ResolveProgressModeOptions {
30
+ verbose?: boolean
31
+ json?: boolean
32
+ target?: ProgressOutputTarget
33
+ env?: NodeJS.ProcessEnv
34
+ isTTY?: boolean
35
+ }
36
+
37
+ export type ProgressStepState = 'pending' | 'running' | 'complete' | 'failed' | 'skipped'
38
+
39
+ export interface ProgressStepDefinition {
40
+ id: string
41
+ label: string
42
+ }
43
+
44
+ const PROGRESS_MARKER_PREFIX = 'BOXDOWN_PROGRESS:'
45
+ const DEFAULT_FAILURE_TAIL_LINES = 20
46
+ const DEFAULT_SPINNER_FRAMES = ['◒', '◐', '◓', '◑'] as const
47
+ const DEFAULT_SPINNER_INTERVAL_MS = 120
48
+
49
+ interface ActiveSpinner {
50
+ message: string
51
+ frameIndex: number
52
+ timer?: ReturnType<typeof setInterval>
53
+ tty: boolean
54
+ }
55
+
56
+ interface ProgressStep extends ProgressStepDefinition {
57
+ state: ProgressStepState
58
+ }
59
+
60
+ function writeLine (target: ProgressOutputTarget, message: string): void {
61
+ const stream = target === 'stderr' ? process.stderr : process.stdout
62
+ stream.write(`${message}\n`)
63
+ }
64
+
65
+ function writeRaw (target: ProgressOutputTarget, message: string): void {
66
+ const stream = target === 'stderr' ? process.stderr : process.stdout
67
+ stream.write(message)
68
+ }
69
+
70
+ function targetIsTTY (target: ProgressOutputTarget): boolean {
71
+ const stream = target === 'stderr' ? process.stderr : process.stdout
72
+ return stream.isTTY === true
73
+ }
74
+
75
+ function isCiEnvironment (env: NodeJS.ProcessEnv): boolean {
76
+ const ci = env.CI
77
+ return ci !== undefined && ci !== '' && ci !== '0' && ci !== 'false'
78
+ }
79
+
80
+ function normalizeMessage (message: string): string {
81
+ return message.trim().replace(/\s+/g, ' ')
82
+ }
83
+
84
+ export function resolveProgressMode (options: ResolveProgressModeOptions = {}): ProgressMode {
85
+ if (options.json === true) {
86
+ return 'none'
87
+ }
88
+
89
+ if (options.verbose === true) {
90
+ return 'verbose'
91
+ }
92
+
93
+ if (isCiEnvironment(options.env ?? process.env)) {
94
+ return 'verbose'
95
+ }
96
+
97
+ const target = options.target ?? 'stdout'
98
+ const isTTY = options.isTTY ?? targetIsTTY(target)
99
+
100
+ return isTTY ? 'interactive' : 'verbose'
101
+ }
102
+
103
+ export class ProgressReporter {
104
+ readonly mode: ProgressMode
105
+ readonly verbose: boolean
106
+ readonly target: ProgressOutputTarget
107
+ readonly #write: ProgressWriter
108
+ readonly #writeRaw: ProgressRawWriter
109
+ readonly #isTTY: boolean
110
+ readonly #spinnerFrames: readonly string[]
111
+ readonly #spinnerIntervalMs: number
112
+ #sectionPrinted = false
113
+ #sectionOpen = false
114
+ #spinner: ActiveSpinner | undefined
115
+ #steps: ProgressStep[] = []
116
+ #renderedStepLineCount = 0
117
+ #stepFrameIndex = 0
118
+ #stepTimer: ReturnType<typeof setInterval> | undefined
119
+
120
+ constructor (options: ProgressReporterOptions = {}) {
121
+ this.mode = options.mode ?? (options.verbose === true ? 'verbose' : 'interactive')
122
+ this.verbose = this.mode === 'verbose'
123
+ this.target = options.target ?? 'stdout'
124
+ this.#write = options.write ?? writeLine
125
+ this.#writeRaw = options.writeRaw ?? writeRaw
126
+ this.#isTTY = options.isTTY ?? targetIsTTY(this.target)
127
+ this.#spinnerFrames = options.spinnerFrames ?? DEFAULT_SPINNER_FRAMES
128
+ this.#spinnerIntervalMs = options.spinnerIntervalMs ?? DEFAULT_SPINNER_INTERVAL_MS
129
+ }
130
+
131
+ section (title: string): void {
132
+ if (this.mode !== 'interactive') {
133
+ return
134
+ }
135
+
136
+ if (this.#sectionOpen) {
137
+ this.end()
138
+ } else if (this.#sectionPrinted) {
139
+ this.#write(this.target, '')
140
+ }
141
+
142
+ this.#write(this.target, formatPromptTitle(title))
143
+ this.#sectionPrinted = true
144
+ this.#sectionOpen = true
145
+ }
146
+
147
+ end (): void {
148
+ this.stopSpinner()
149
+ this.#stopStepTimer()
150
+ this.#steps = []
151
+ this.#renderedStepLineCount = 0
152
+
153
+ if (this.mode !== 'interactive') {
154
+ return
155
+ }
156
+
157
+ if (!this.#sectionOpen) {
158
+ return
159
+ }
160
+
161
+ this.#write(this.target, formatPromptEnd())
162
+ this.#sectionOpen = false
163
+ }
164
+
165
+ item (message: string): void {
166
+ if (this.mode !== 'interactive') {
167
+ return
168
+ }
169
+
170
+ this.#writeLine(`${promptRail()} ${selectedMark()} ${message}`)
171
+ }
172
+
173
+ detail (message: string): void {
174
+ if (this.mode !== 'interactive') {
175
+ return
176
+ }
177
+
178
+ this.#writeLine(`${promptRail()} ${color(message, 'dim')}`)
179
+ }
180
+
181
+ warn (message: string): void {
182
+ if (this.mode === 'none') {
183
+ return
184
+ }
185
+
186
+ if (this.mode === 'verbose') {
187
+ this.#write(this.target, `Warning: ${message}`)
188
+ return
189
+ }
190
+
191
+ this.#writeLine(`${promptRail()} ${color('!', 'dim')} ${message}`)
192
+ }
193
+
194
+ marker (message: string): void {
195
+ if (this.#steps.length > 0) {
196
+ return
197
+ }
198
+
199
+ const normalized = normalizeMessage(message)
200
+
201
+ if (normalized.length > 0) {
202
+ this.item(normalized)
203
+ }
204
+ }
205
+
206
+ setSteps (steps: readonly ProgressStepDefinition[]): void {
207
+ this.#stopStepTimer()
208
+ this.#steps = steps.map((step) => ({
209
+ ...step,
210
+ state: 'pending'
211
+ }))
212
+ this.#stepFrameIndex = 0
213
+ this.#renderedStepLineCount = 0
214
+ this.#renderChecklist()
215
+ }
216
+
217
+ isChecklistActive (): boolean {
218
+ return this.#steps.length > 0
219
+ }
220
+
221
+ startStep (id: string): void {
222
+ this.#updateStep(id, 'running')
223
+ this.#startStepTimer()
224
+ }
225
+
226
+ completeStep (id: string): void {
227
+ this.#updateStep(id, 'complete')
228
+ this.#stopStepTimerIfIdle()
229
+ }
230
+
231
+ failStep (id: string): void {
232
+ this.#updateStep(id, 'failed')
233
+ this.#stopStepTimerIfIdle()
234
+ }
235
+
236
+ skipStep (id: string): void {
237
+ this.#updateStep(id, 'skipped')
238
+ this.#stopStepTimerIfIdle()
239
+ }
240
+
241
+ hasStep (id: string): boolean {
242
+ return this.#steps.some((step) => step.id === id)
243
+ }
244
+
245
+ startSpinner (message: string): void {
246
+ if (this.mode !== 'interactive') {
247
+ return
248
+ }
249
+
250
+ const normalized = normalizeMessage(message)
251
+ if (normalized.length === 0) {
252
+ return
253
+ }
254
+
255
+ this.stopSpinner()
256
+ this.#spinner = {
257
+ message: normalized,
258
+ frameIndex: 0,
259
+ tty: this.#isTTY
260
+ }
261
+
262
+ if (this.#spinner.tty) {
263
+ this.#renderSpinner()
264
+ this.#spinner.timer = setInterval(() => {
265
+ this.tickSpinner()
266
+ }, this.#spinnerIntervalMs)
267
+ this.#spinner.timer.unref?.()
268
+ return
269
+ }
270
+
271
+ this.#writeLine(`${promptRail()} ${color(this.#spinnerFrames[0] ?? '◒', 'cyan')} ${normalized}`)
272
+ }
273
+
274
+ tickSpinner (): void {
275
+ if (this.#steps.some((step) => step.state === 'running')) {
276
+ this.#stepFrameIndex += 1
277
+ this.#renderChecklist()
278
+ return
279
+ }
280
+
281
+ const spinner = this.#spinner
282
+ if (spinner === undefined || !spinner.tty) {
283
+ return
284
+ }
285
+
286
+ spinner.frameIndex += 1
287
+ this.#renderSpinner()
288
+ }
289
+
290
+ stopSpinner (status: 'complete' | 'clear' = 'clear'): void {
291
+ const spinner = this.#spinner
292
+ if (spinner === undefined) {
293
+ return
294
+ }
295
+
296
+ if (spinner.timer !== undefined) {
297
+ clearInterval(spinner.timer)
298
+ }
299
+
300
+ if (spinner.tty) {
301
+ this.#clearSpinnerLine()
302
+ }
303
+
304
+ this.#spinner = undefined
305
+
306
+ if (status === 'complete') {
307
+ this.item(spinner.message)
308
+ }
309
+ }
310
+
311
+ commandEnv (env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
312
+ return {
313
+ ...(env ?? {}),
314
+ BOXDOWN_VERBOSE: this.verbose ? '1' : '0',
315
+ BOXDOWN_PROGRESS: this.mode === 'interactive' ? '1' : '0'
316
+ }
317
+ }
318
+
319
+ #writeLine (message: string): void {
320
+ const spinner = this.#spinner
321
+
322
+ if (spinner !== undefined && spinner.tty) {
323
+ this.#clearSpinnerLine()
324
+ this.#write(this.target, message)
325
+ this.#renderSpinner()
326
+ return
327
+ }
328
+
329
+ this.#write(this.target, message)
330
+ }
331
+
332
+ #renderSpinner (): void {
333
+ const spinner = this.#spinner
334
+ if (spinner === undefined) {
335
+ return
336
+ }
337
+
338
+ const frame = this.#spinnerFrames[spinner.frameIndex % this.#spinnerFrames.length] ?? '◒'
339
+ this.#writeRaw(this.target, `\r\u001B[2K${promptRail()} ${color(frame, 'cyan')} ${spinner.message}`)
340
+ }
341
+
342
+ #clearSpinnerLine (): void {
343
+ this.#writeRaw(this.target, '\r\u001B[2K')
344
+ }
345
+
346
+ #updateStep (id: string, state: ProgressStepState): void {
347
+ const index = this.#steps.findIndex((step) => step.id === id)
348
+
349
+ if (index === -1) {
350
+ return
351
+ }
352
+
353
+ const step = this.#steps[index]
354
+ if (step === undefined) {
355
+ return
356
+ }
357
+
358
+ this.#steps[index] = {
359
+ ...step,
360
+ state
361
+ }
362
+
363
+ this.#renderChecklist()
364
+ }
365
+
366
+ #renderChecklist (): void {
367
+ if (this.mode !== 'interactive' || this.#steps.length === 0) {
368
+ return
369
+ }
370
+
371
+ const lines = this.#steps.map((step) => this.#formatStep(step))
372
+
373
+ if (this.#isTTY) {
374
+ if (this.#renderedStepLineCount > 0) {
375
+ this.#writeRaw(this.target, `\u001B[${this.#renderedStepLineCount}A`)
376
+ }
377
+
378
+ for (const line of lines) {
379
+ this.#writeRaw(this.target, `\u001B[2K\r${line}\n`)
380
+ }
381
+
382
+ this.#renderedStepLineCount = lines.length
383
+ return
384
+ }
385
+
386
+ if (this.#renderedStepLineCount === 0) {
387
+ for (const line of lines) {
388
+ this.#write(this.target, line)
389
+ }
390
+ this.#renderedStepLineCount = lines.length
391
+ }
392
+ }
393
+
394
+ #formatStep (step: ProgressStep): string {
395
+ const label = step.state === 'skipped' ? color(step.label, 'dim') : step.label
396
+ return `${promptRail()} ${this.#stepMark(step.state)} ${label}`
397
+ }
398
+
399
+ #stepMark (state: ProgressStepState): string {
400
+ if (state === 'running') {
401
+ const frame = this.#spinnerFrames[this.#stepFrameIndex % this.#spinnerFrames.length] ?? '◐'
402
+ return color(frame, 'cyan')
403
+ }
404
+
405
+ if (state === 'complete') {
406
+ return color('✔', 'green')
407
+ }
408
+
409
+ if (state === 'failed') {
410
+ return color('!', 'dim')
411
+ }
412
+
413
+ return color('□', 'dim')
414
+ }
415
+
416
+ #startStepTimer (): void {
417
+ if (this.mode !== 'interactive' || !this.#isTTY || this.#stepTimer !== undefined) {
418
+ return
419
+ }
420
+
421
+ this.#stepTimer = setInterval(() => {
422
+ this.tickSpinner()
423
+ }, this.#spinnerIntervalMs)
424
+ this.#stepTimer.unref?.()
425
+ }
426
+
427
+ #stopStepTimerIfIdle (): void {
428
+ if (this.#steps.some((step) => step.state === 'running')) {
429
+ return
430
+ }
431
+
432
+ this.#stopStepTimer()
433
+ }
434
+
435
+ #stopStepTimer (): void {
436
+ if (this.#stepTimer === undefined) {
437
+ return
438
+ }
439
+
440
+ clearInterval(this.#stepTimer)
441
+ this.#stepTimer = undefined
442
+ }
443
+ }
444
+
445
+ export function createProgress (options: ProgressReporterOptions = {}): ProgressReporter {
446
+ return new ProgressReporter(options)
447
+ }
448
+
449
+ export function progressMarkerLine (message: string): string {
450
+ return `${PROGRESS_MARKER_PREFIX} ${message}`
451
+ }
452
+
453
+ function isProgressMarkerLine (line: string): boolean {
454
+ return line.trimStart().startsWith(PROGRESS_MARKER_PREFIX)
455
+ }
456
+
457
+ function progressMarkerMessage (line: string): string | undefined {
458
+ const trimmed = line.trimStart()
459
+
460
+ if (!trimmed.startsWith(PROGRESS_MARKER_PREFIX)) {
461
+ return undefined
462
+ }
463
+
464
+ return trimmed.slice(PROGRESS_MARKER_PREFIX.length).trim()
465
+ }
466
+
467
+ function createMarkerSink (progress: ProgressReporter): {
468
+ write: (chunk: Buffer) => void
469
+ flush: () => void
470
+ } {
471
+ let pending = ''
472
+
473
+ function processLine (line: string): void {
474
+ const message = progressMarkerMessage(line)
475
+
476
+ if (message !== undefined) {
477
+ progress.marker(message)
478
+ }
479
+ }
480
+
481
+ return {
482
+ write: (chunk: Buffer) => {
483
+ pending += chunk.toString('utf8')
484
+
485
+ const lines = pending.split(/\r?\n/u)
486
+ pending = lines.pop() ?? ''
487
+
488
+ for (const line of lines) {
489
+ processLine(line)
490
+ }
491
+ },
492
+ flush: () => {
493
+ if (pending.length > 0) {
494
+ processLine(pending)
495
+ pending = ''
496
+ }
497
+ }
498
+ }
499
+ }
500
+
501
+ function outputWithoutProgressMarkers (output: string): string {
502
+ return output
503
+ .split(/\r?\n/u)
504
+ .filter((line) => !isProgressMarkerLine(line))
505
+ .join('\n')
506
+ }
507
+
508
+ function tailLines (output: string, maxLines: number): string[] {
509
+ return output
510
+ .split(/\r?\n/u)
511
+ .map((line) => line.trimEnd())
512
+ .filter((line) => line.trim().length > 0)
513
+ .slice(-maxLines)
514
+ }
515
+
516
+ export function formatCommandFailure (label: string, result: CommandResult, options: { tailLines?: number } = {}): string {
517
+ const maxLines = options.tailLines ?? DEFAULT_FAILURE_TAIL_LINES
518
+ const stderrTail = tailLines(outputWithoutProgressMarkers(result.stderr), maxLines)
519
+ const stdoutTail = tailLines(outputWithoutProgressMarkers(result.stdout), Math.max(0, maxLines - stderrTail.length))
520
+ const lines = [
521
+ `${label} failed with exit code ${result.code}.`,
522
+ 'Rerun with --verbose to see full command output.'
523
+ ]
524
+
525
+ if (stderrTail.length > 0) {
526
+ lines.push('', 'stderr tail:', ...stderrTail.map((line) => ` ${line}`))
527
+ }
528
+
529
+ if (stdoutTail.length > 0) {
530
+ lines.push('', 'stdout tail:', ...stdoutTail.map((line) => ` ${line}`))
531
+ }
532
+
533
+ return lines.join('\n')
534
+ }
535
+
536
+ export async function runProgressCommand (
537
+ label: string,
538
+ command: string,
539
+ args: string[],
540
+ options: ProgressCommandOptions = {}
541
+ ): Promise<CommandResult> {
542
+ const progress = options.progress
543
+ const verbose = progress?.verbose ?? true
544
+ const markerSink = progress !== undefined && !verbose ? createMarkerSink(progress) : undefined
545
+ const checklistStepId = progress !== undefined && options.stepId !== undefined && progress.hasStep(options.stepId)
546
+ ? options.stepId
547
+ : undefined
548
+
549
+ if (progress !== undefined && !verbose && checklistStepId !== undefined) {
550
+ progress.startStep(checklistStepId)
551
+ } else if (progress !== undefined && !verbose && options.spinnerLabel !== undefined) {
552
+ progress.startSpinner(options.spinnerLabel)
553
+ }
554
+
555
+ try {
556
+ const result = await runBuffered(command, args, {
557
+ cwd: options.cwd,
558
+ env: progress?.commandEnv(options.env) ?? options.env,
559
+ input: options.input,
560
+ logger: options.logger,
561
+ mirrorStdout: verbose ? (options.verboseStdout ?? 'stdout') : false,
562
+ mirrorStderr: verbose ? (options.verboseStderr ?? 'stderr') : false,
563
+ onStdout: markerSink?.write,
564
+ onStderr: markerSink?.write
565
+ })
566
+
567
+ markerSink?.flush()
568
+ if (checklistStepId !== undefined) {
569
+ if (result.code === 0) {
570
+ progress?.completeStep(checklistStepId)
571
+ } else {
572
+ progress?.failStep(checklistStepId)
573
+ }
574
+ } else {
575
+ progress?.stopSpinner(result.code === 0 ? 'complete' : 'clear')
576
+ }
577
+ return result
578
+ } catch (error) {
579
+ markerSink?.flush()
580
+ if (checklistStepId !== undefined) {
581
+ progress?.failStep(checklistStepId)
582
+ } else {
583
+ progress?.stopSpinner()
584
+ }
585
+ throw error
586
+ }
587
+ }
588
+
589
+ export function assertProgressCommandSucceeded (label: string, result: CommandResult, message: string): void {
590
+ if (result.code !== 0) {
591
+ throw new Error(`${message}\n${formatCommandFailure(label, result)}`)
592
+ }
593
+ }