or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ask.mdgetopt.mdindex.mdprogress-bar.mdread.mdreadLine.md

ask.mddocs/

0

# Interactive Terminal Questions

1

2

Ask questions in terminal with option validation, retry mechanisms, and customizable input streams for building interactive command-line applications.

3

4

## Capabilities

5

6

### Main Ask Function

7

8

Displays questions and waits for user responses with optional validation and retry logic.

9

10

```typescript { .api }

11

/**

12

* Ask an interactive question and wait for user response

13

* @param question - The question text to display

14

* @param config - Configuration options for validation and behavior

15

* @returns Promise resolving to the user's answer as string

16

*/

17

function ask(question: string, config?: AskConfig): Promise<string>;

18

19

interface AskConfig {

20

/** Array of valid answer options for validation */

21

options?: string[];

22

/** Maximum number of retry attempts on invalid input (default: 3) */

23

maxRetries?: number;

24

/** Custom input stream (defaults to process.stdin) */

25

inputStream?: any;

26

}

27

```

28

29

**Usage Examples:**

30

31

```typescript

32

import { ask } from "stdio";

33

34

// Basic question

35

const name = await ask("What's your name?");

36

console.log(`Hello, ${name}!`);

37

38

// Multiple choice with validation

39

const choice = await ask("Choose an option", {

40

options: ['yes', 'no', 'maybe']

41

});

42

console.log(`You chose: ${choice}`);

43

44

// Custom retry limit

45

const action = await ask("Confirm action", {

46

options: ['confirm', 'cancel'],

47

maxRetries: 5

48

});

49

50

// Free-form input (no validation)

51

const feedback = await ask("Any additional comments?");

52

53

// Complex interactive flow

54

const language = await ask("Select language", {

55

options: ['javascript', 'typescript', 'python']

56

});

57

58

const framework = language === 'javascript' || language === 'typescript'

59

? await ask("Select framework", { options: ['react', 'vue', 'angular'] })

60

: await ask("Select framework", { options: ['django', 'flask', 'fastapi'] });

61

62

console.log(`Selected: ${language} with ${framework}`);

63

```

64

65

### Configuration Interface

66

67

Comprehensive configuration for question behavior and validation.

68

69

```typescript { .api }

70

interface AskConfig {

71

/**

72

* Valid answer options - if provided, only these answers are accepted

73

* Invalid answers trigger retry with helpful error message

74

*/

75

options?: string[];

76

77

/**

78

* Maximum retry attempts for invalid answers (default: 3)

79

* After exhausting retries, promise rejects with error

80

*/

81

maxRetries?: number;

82

83

/**

84

* Custom input stream for testing or alternative input sources

85

* Defaults to process.stdin for normal terminal interaction

86

*/

87

inputStream?: any;

88

}

89

```

90

91

### Question Display Format

92

93

Questions are automatically formatted with helpful context:

94

95

```typescript

96

// Basic question

97

await ask("Enter your email");

98

// Displays: "Enter your email: "

99

100

// With options

101

await ask("Continue?", { options: ['yes', 'no'] });

102

// Displays: "Continue? [yes/no]: "

103

104

// Multiple options

105

await ask("Select environment", {

106

options: ['development', 'staging', 'production']

107

});

108

// Displays: "Select environment [development/staging/production]: "

109

```

110

111

## Advanced Features

112

113

### Input Validation and Retry Logic

114

115

Automatic validation with user-friendly error handling:

116

117

```typescript

118

import { ask } from "stdio";

119

120

// This will retry until valid input

121

const answer = await ask("Are you sure?", {

122

options: ['yes', 'no'],

123

maxRetries: 3

124

});

125

126

// Invalid inputs show helpful messages:

127

// User types "maybe"

128

// Output: "Unexpected answer. 2 retries left."

129

// Prompt: "Are you sure? [yes/no]: "

130

131

// After 3 invalid attempts, promise rejects with:

132

// Error: "Retries spent"

133

```

134

135

### Custom Input Streams

136

137

Support for alternative input sources for testing and automation:

138

139

```typescript

140

import { ask } from "stdio";

141

import { Readable } from 'stream';

142

143

// Create mock input stream for testing

144

const mockInput = new Readable({

145

read() {

146

this.push('yes\n');

147

this.push(null); // End stream

148

}

149

});

150

151

const response = await ask("Proceed?", {

152

options: ['yes', 'no'],

153

inputStream: mockInput

154

});

155

// response === 'yes'

156

157

// Testing interactive flows

158

async function testInteractiveFlow() {

159

const responses = ['john', 'developer', 'yes'];

160

let responseIndex = 0;

161

162

const mockStream = new Readable({

163

read() {

164

if (responseIndex < responses.length) {

165

this.push(responses[responseIndex++] + '\n');

166

} else {

167

this.push(null);

168

}

169

}

170

});

171

172

const name = await ask("Name?", { inputStream: mockStream });

173

const role = await ask("Role?", { inputStream: mockStream });

174

const confirm = await ask("Confirm?", {

175

options: ['yes', 'no'],

176

inputStream: mockStream

177

});

178

179

return { name, role, confirm };

180

}

181

```

182

183

### Error Handling

184

185

Comprehensive error handling for various failure scenarios:

186

187

```typescript

188

try {

189

const answer = await ask("Choose option", {

190

options: ['a', 'b', 'c'],

191

maxRetries: 2

192

});

193

console.log(`Selected: ${answer}`);

194

} catch (error) {

195

if (error.message === 'Retries spent') {

196

console.error('Too many invalid attempts');

197

process.exit(1);

198

} else {

199

console.error('Unexpected error:', error.message);

200

}

201

}

202

203

// Stream-related errors

204

try {

205

const brokenStream = new BrokenReadableStream();

206

await ask("Question?", { inputStream: brokenStream });

207

} catch (error) {

208

console.error('Input stream error:', error);

209

}

210

```

211

212

### Integration Patterns

213

214

Common patterns for building interactive applications:

215

216

**Setup Wizard:**

217

218

```typescript

219

async function setupWizard() {

220

console.log('=== Application Setup ===');

221

222

const config = {};

223

224

config.name = await ask("Project name?");

225

226

config.type = await ask("Project type?", {

227

options: ['web', 'api', 'cli']

228

});

229

230

if (config.type === 'web') {

231

config.framework = await ask("Frontend framework?", {

232

options: ['react', 'vue', 'angular']

233

});

234

}

235

236

config.database = await ask("Database?", {

237

options: ['mysql', 'postgresql', 'mongodb', 'none']

238

});

239

240

const proceed = await ask("Create project with these settings?", {

241

options: ['yes', 'no']

242

});

243

244

if (proceed === 'yes') {

245

await createProject(config);

246

} else {

247

console.log('Setup cancelled');

248

}

249

}

250

```

251

252

**Confirmation Dialogs:**

253

254

```typescript

255

async function confirmAction(action: string): Promise<boolean> {

256

const response = await ask(`Are you sure you want to ${action}?`, {

257

options: ['yes', 'no'],

258

maxRetries: 1

259

});

260

return response === 'yes';

261

}

262

263

// Usage

264

if (await confirmAction('delete all files')) {

265

await deleteFiles();

266

} else {

267

console.log('Operation cancelled');

268

}

269

```

270

271

**Menu Selection:**

272

273

```typescript

274

async function showMenu(): Promise<string> {

275

console.log('Available actions:');

276

console.log('1. Create new item');

277

console.log('2. List items');

278

console.log('3. Delete item');

279

console.log('4. Exit');

280

281

const choice = await ask("Select action", {

282

options: ['1', '2', '3', '4'],

283

maxRetries: 5

284

});

285

286

const actions = {

287

'1': 'create',

288

'2': 'list',

289

'3': 'delete',

290

'4': 'exit'

291

};

292

293

return actions[choice];

294

}

295

```

296

297

## Constants and Defaults

298

299

```typescript { .api }

300

/** Default maximum retry attempts */

301

const DEFAULT_MAX_RETRIES = 3;

302

```

303

304

The ask function uses sensible defaults:

305

- **Input Stream**: `process.stdin` for normal terminal interaction

306

- **Max Retries**: 3 attempts before rejecting with error

307

- **Output**: Questions display to `process.stdout` with formatted options

308

- **Validation**: Case-sensitive exact matching for option validation