Skip to content

Commit 71bc636

Browse files
committed
fix(cli): deduplicate isInteractiveTTY and stop spinner on error
Extract shared isInteractiveTTY() to prompts.ts and add try/catch to withSpinner so the spinner is stopped if the task throws, preventing garbled terminal output on error.
1 parent bc478cd commit 71bc636

3 files changed

Lines changed: 20 additions & 14 deletions

File tree

src/cli/commands/init.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from 'node:path';
44
import * as os from 'node:os';
55
import * as clack from '@clack/prompts';
66
import { getResourceRoot } from '../../core/resource-root.ts';
7-
import { createPrompter, type Prompter } from '../interactive/prompts.ts';
7+
import { createPrompter, isInteractiveTTY, type Prompter } from '../interactive/prompts.ts';
88

99
type SkillType = 'mcp' | 'cli';
1010

@@ -88,10 +88,6 @@ function resolveDestinationPath(inputPath: string): string {
8888
return path.resolve(expandHomePrefix(inputPath));
8989
}
9090

91-
function isInteractiveTTY(): boolean {
92-
return process.stdin.isTTY === true && process.stdout.isTTY === true;
93-
}
94-
9591
async function promptConfirm(question: string): Promise<boolean> {
9692
if (!isInteractiveTTY()) {
9793
return false;

src/cli/commands/setup.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import {
1313
persistProjectConfigPatch,
1414
type ProjectConfig,
1515
} from '../../utils/project-config.ts';
16-
import { createPrompter, type Prompter, type SelectOption } from '../interactive/prompts.ts';
16+
import {
17+
createPrompter,
18+
isInteractiveTTY,
19+
type Prompter,
20+
type SelectOption,
21+
} from '../interactive/prompts.ts';
1722
import type { FileSystemExecutor } from '../../utils/FileSystemExecutor.ts';
1823
import type { CommandExecutor } from '../../utils/CommandExecutor.ts';
1924
import { createDoctorDependencies } from '../../mcp/tools/doctor/lib/doctor.deps.ts';
@@ -52,10 +57,6 @@ function showPromptHelp(helpText: string, quietOutput: boolean): void {
5257
clack.log.message(helpText);
5358
}
5459

55-
function isInteractiveTTY(): boolean {
56-
return process.stdin.isTTY === true && process.stdout.isTTY === true;
57-
}
58-
5960
async function withSpinner<T>(opts: {
6061
isTTY: boolean;
6162
quietOutput: boolean;
@@ -69,9 +70,14 @@ async function withSpinner<T>(opts: {
6970

7071
const s = clack.spinner();
7172
s.start(opts.startMessage);
72-
const result = await opts.task();
73-
s.stop(opts.stopMessage);
74-
return result;
73+
try {
74+
const result = await opts.task();
75+
s.stop(opts.stopMessage);
76+
return result;
77+
} catch (error) {
78+
s.stop(opts.startMessage);
79+
throw error;
80+
}
7581
}
7682

7783
function valuesEqual(left: unknown, right: unknown): boolean {

src/cli/interactive/prompts.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,12 @@ function createTtyPrompter(): Prompter {
136136
};
137137
}
138138

139+
export function isInteractiveTTY(): boolean {
140+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
141+
}
142+
139143
export function createPrompter(): Prompter {
140-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
144+
if (!isInteractiveTTY()) {
141145
return createNonInteractivePrompter();
142146
}
143147

0 commit comments

Comments
 (0)