inquirer-ai 0.2.1 → 0.3.0
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.
- package/README.md +642 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +35 -5
- package/dist/agent.js.map +1 -1
- package/dist/choice.d.ts.map +1 -1
- package/dist/choice.js +3 -0
- package/dist/choice.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/prompts/base.d.ts.map +1 -1
- package/dist/prompts/base.js +19 -4
- package/dist/prompts/base.js.map +1 -1
- package/dist/prompts/checkbox.d.ts +2 -0
- package/dist/prompts/checkbox.d.ts.map +1 -1
- package/dist/prompts/checkbox.js +13 -2
- package/dist/prompts/checkbox.js.map +1 -1
- package/dist/prompts/expand.d.ts.map +1 -1
- package/dist/prompts/expand.js +9 -5
- package/dist/prompts/expand.js.map +1 -1
- package/dist/prompts/number.d.ts +5 -0
- package/dist/prompts/number.d.ts.map +1 -1
- package/dist/prompts/number.js +42 -5
- package/dist/prompts/number.js.map +1 -1
- package/dist/prompts/search.d.ts +4 -2
- package/dist/prompts/search.d.ts.map +1 -1
- package/dist/prompts/search.js +110 -15
- package/dist/prompts/search.js.map +1 -1
- package/dist/prompts/select.d.ts.map +1 -1
- package/dist/prompts/select.js +11 -1
- package/dist/prompts/select.js.map +1 -1
- package/dist/prompts/text.d.ts +4 -0
- package/dist/prompts/text.d.ts.map +1 -1
- package/dist/prompts/text.js +13 -4
- package/dist/prompts/text.js.map +1 -1
- package/dist/socket.d.ts +21 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +334 -0
- package/dist/socket.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
# inquirer-ai
|
|
2
|
+
|
|
3
|
+
Interactive CLI prompts for humans and AI agents, written in TypeScript.
|
|
4
|
+
|
|
5
|
+
When stdin is a TTY, prompts render a terminal UI with cursor navigation,
|
|
6
|
+
key bindings, and styled output. When stdin is not a TTY
|
|
7
|
+
(or `INQUIRER_AI_MODE=agent`), prompts communicate via a JSON line protocol on
|
|
8
|
+
stdout/stdin so that AI agents can drive CLI tools programmatically.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install inquirer-ai
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Node.js 18+.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { text, confirm, select } from "inquirer-ai";
|
|
22
|
+
|
|
23
|
+
const name = await text({
|
|
24
|
+
message: "Project name",
|
|
25
|
+
validate: (s) => (s.length > 0 ? true : "Cannot be empty"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const useTS = await confirm({
|
|
29
|
+
message: "Use TypeScript?",
|
|
30
|
+
default: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const template = await select({
|
|
34
|
+
message: "Template",
|
|
35
|
+
choices: [
|
|
36
|
+
{ name: "Web API", value: "web-api" },
|
|
37
|
+
{ name: "CLI Tool", value: "cli-tool" },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log(`Creating ${name} with template ${template} (TS: ${useTS})`);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Prompt Types
|
|
45
|
+
|
|
46
|
+
All 12 prompt types follow the same pattern: pass a config object, get back a
|
|
47
|
+
typed promise.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Text
|
|
52
|
+
|
|
53
|
+
Single-line text input.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
function text(config: TextConfig): Promise<string>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface TextConfig {
|
|
61
|
+
message: string;
|
|
62
|
+
default?: string | null;
|
|
63
|
+
validate?: ValidateFn<string>;
|
|
64
|
+
filter?: FilterFn<string>;
|
|
65
|
+
transformer?: TransformerFn<string>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { text } from "inquirer-ai";
|
|
71
|
+
|
|
72
|
+
const name = await text({
|
|
73
|
+
message: "Your name",
|
|
74
|
+
default: "World",
|
|
75
|
+
filter: (s) => s.trim(),
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Confirm
|
|
82
|
+
|
|
83
|
+
Yes/no boolean prompt.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
function confirm(config: ConfirmConfig): Promise<boolean>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
interface ConfirmConfig {
|
|
91
|
+
message: string;
|
|
92
|
+
default?: boolean | null; // default: false
|
|
93
|
+
validate?: ValidateFn<boolean>;
|
|
94
|
+
filter?: FilterFn<boolean>;
|
|
95
|
+
transformer?: TransformerFn<boolean>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { confirm } from "inquirer-ai";
|
|
101
|
+
|
|
102
|
+
const ok = await confirm({
|
|
103
|
+
message: "Continue?",
|
|
104
|
+
default: true,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Select
|
|
111
|
+
|
|
112
|
+
Single-choice list with cursor navigation.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
function select<V = unknown>(config: SelectConfig<V>): Promise<V>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface SelectConfig<V = unknown> {
|
|
120
|
+
message: string;
|
|
121
|
+
choices: RawChoice<V>[];
|
|
122
|
+
default?: V | null;
|
|
123
|
+
pageSize?: number; // default: 10
|
|
124
|
+
loop?: boolean; // default: true
|
|
125
|
+
validate?: ValidateFn<V>;
|
|
126
|
+
filter?: FilterFn<V>;
|
|
127
|
+
transformer?: TransformerFn<V>;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { select, createSeparator } from "inquirer-ai";
|
|
133
|
+
|
|
134
|
+
const tmpl = await select({
|
|
135
|
+
message: "Template",
|
|
136
|
+
choices: [
|
|
137
|
+
{ name: "Web API", value: "web-api", description: "FastAPI + PostgreSQL" },
|
|
138
|
+
{ name: "CLI Tool", value: "cli-tool" },
|
|
139
|
+
createSeparator("── Experimental ──"),
|
|
140
|
+
{ name: "gRPC Service", value: "grpc" },
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Key bindings in terminal mode: `up`/`k`, `down`/`j`, `enter` to confirm,
|
|
146
|
+
`ctrl+c` to abort.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### Checkbox
|
|
151
|
+
|
|
152
|
+
Multi-select list with toggle.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
function checkbox<V = unknown>(config: CheckboxConfig<V>): Promise<V[]>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface CheckboxConfig<V = unknown> {
|
|
160
|
+
message: string;
|
|
161
|
+
choices: RawChoice<V>[];
|
|
162
|
+
default?: V[] | null;
|
|
163
|
+
pageSize?: number; // default: 10
|
|
164
|
+
loop?: boolean; // default: true
|
|
165
|
+
validate?: ValidateFn<V[]>;
|
|
166
|
+
filter?: FilterFn<V[]>;
|
|
167
|
+
transformer?: TransformerFn<V[]>;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { checkbox } from "inquirer-ai";
|
|
173
|
+
|
|
174
|
+
const features = await checkbox({
|
|
175
|
+
message: "Features",
|
|
176
|
+
default: ["docker"],
|
|
177
|
+
choices: [
|
|
178
|
+
{ name: "Docker support", value: "docker" },
|
|
179
|
+
{ name: "CI/CD", value: "ci" },
|
|
180
|
+
{ name: "Load testing", value: "load-test", disabled: "coming soon" },
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Key bindings: `space` to toggle, `a` to toggle all, `enter` to confirm.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### Password
|
|
190
|
+
|
|
191
|
+
Masked text input.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
function password(config: PasswordConfig): Promise<string>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
interface PasswordConfig {
|
|
199
|
+
message: string;
|
|
200
|
+
mask?: string | null; // default: "*"
|
|
201
|
+
validate?: ValidateFn<string>;
|
|
202
|
+
filter?: FilterFn<string>;
|
|
203
|
+
transformer?: TransformerFn<string>;
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { password } from "inquirer-ai";
|
|
209
|
+
|
|
210
|
+
const pw = await password({
|
|
211
|
+
message: "API key",
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### Number
|
|
218
|
+
|
|
219
|
+
Numeric input with optional min/max bounds.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
function number(config: NumberConfig): Promise<number>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
interface NumberConfig {
|
|
227
|
+
message: string;
|
|
228
|
+
default?: number | null;
|
|
229
|
+
min?: number | null;
|
|
230
|
+
max?: number | null;
|
|
231
|
+
floatAllowed?: boolean; // default: true
|
|
232
|
+
validate?: ValidateFn<number>;
|
|
233
|
+
filter?: FilterFn<number>;
|
|
234
|
+
transformer?: TransformerFn<number>;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { number } from "inquirer-ai";
|
|
240
|
+
|
|
241
|
+
const port = await number({
|
|
242
|
+
message: "Port",
|
|
243
|
+
default: 8080,
|
|
244
|
+
min: 1024,
|
|
245
|
+
max: 65535,
|
|
246
|
+
floatAllowed: false,
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### Editor
|
|
253
|
+
|
|
254
|
+
Opens `$VISUAL`, `$EDITOR`, or `vi` for multi-line text input.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
function editor(config: EditorConfig): Promise<string>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
interface EditorConfig {
|
|
262
|
+
message: string;
|
|
263
|
+
default?: string | null;
|
|
264
|
+
postfix?: string; // file extension, default: ".txt"
|
|
265
|
+
validate?: ValidateFn<string>;
|
|
266
|
+
filter?: FilterFn<string>;
|
|
267
|
+
transformer?: TransformerFn<string>;
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { editor } from "inquirer-ai";
|
|
273
|
+
|
|
274
|
+
const body = await editor({
|
|
275
|
+
message: "Commit message",
|
|
276
|
+
postfix: ".md",
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### Search
|
|
283
|
+
|
|
284
|
+
Searchable selection with a dynamic source function.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
function search(config: SearchConfig): Promise<unknown>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
interface SearchConfig {
|
|
292
|
+
message: string;
|
|
293
|
+
source: (term: string) => RawChoice[]; // required
|
|
294
|
+
pageSize?: number; // default: 10
|
|
295
|
+
validate?: ValidateFn<unknown>;
|
|
296
|
+
filter?: FilterFn<unknown>;
|
|
297
|
+
transformer?: TransformerFn<unknown>;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { search } from "inquirer-ai";
|
|
303
|
+
|
|
304
|
+
const pkg = await search({
|
|
305
|
+
message: "Package",
|
|
306
|
+
source: (term) => {
|
|
307
|
+
const all = ["express", "fastify", "koa", "hapi"];
|
|
308
|
+
return all
|
|
309
|
+
.filter((p) => p.includes(term))
|
|
310
|
+
.map((p) => ({ name: p, value: p }));
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
### Rawlist
|
|
318
|
+
|
|
319
|
+
Numbered list -- the user selects by typing a number.
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
function rawlist(config: RawlistConfig): Promise<unknown>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
interface RawlistConfig {
|
|
327
|
+
message: string;
|
|
328
|
+
choices: RawChoice[];
|
|
329
|
+
validate?: ValidateFn<unknown>;
|
|
330
|
+
filter?: FilterFn<unknown>;
|
|
331
|
+
transformer?: TransformerFn<unknown>;
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { rawlist } from "inquirer-ai";
|
|
337
|
+
|
|
338
|
+
const env = await rawlist({
|
|
339
|
+
message: "Environment",
|
|
340
|
+
choices: [
|
|
341
|
+
{ name: "Development", value: "dev" },
|
|
342
|
+
{ name: "Staging", value: "staging" },
|
|
343
|
+
{ name: "Production", value: "prod" },
|
|
344
|
+
],
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
### Expand
|
|
351
|
+
|
|
352
|
+
Compact key-based selection. Each choice has a single-character key.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
function expand(config: ExpandConfig): Promise<unknown>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
interface ExpandChoice {
|
|
360
|
+
key: string;
|
|
361
|
+
name: string;
|
|
362
|
+
value: unknown;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
interface ExpandConfig {
|
|
366
|
+
message: string;
|
|
367
|
+
choices: ExpandChoice[];
|
|
368
|
+
validate?: ValidateFn<unknown>;
|
|
369
|
+
filter?: FilterFn<unknown>;
|
|
370
|
+
transformer?: TransformerFn<unknown>;
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { expand } from "inquirer-ai";
|
|
376
|
+
|
|
377
|
+
const action = await expand({
|
|
378
|
+
message: "Conflict on file.txt",
|
|
379
|
+
choices: [
|
|
380
|
+
{ key: "y", name: "Overwrite", value: "overwrite" },
|
|
381
|
+
{ key: "n", name: "Skip", value: "skip" },
|
|
382
|
+
{ key: "d", name: "Show diff", value: "diff" },
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The user types `h` or `help` to see the full list.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
### Path
|
|
392
|
+
|
|
393
|
+
File or directory path input.
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
function path(config: PathConfig): Promise<string>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
interface PathConfig {
|
|
401
|
+
message: string;
|
|
402
|
+
default?: string | null;
|
|
403
|
+
onlyDirectories?: boolean; // default: false
|
|
404
|
+
validate?: ValidateFn<string>;
|
|
405
|
+
filter?: FilterFn<string>;
|
|
406
|
+
transformer?: TransformerFn<string>;
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { path } from "inquirer-ai";
|
|
412
|
+
|
|
413
|
+
const dir = await path({
|
|
414
|
+
message: "Output directory",
|
|
415
|
+
default: "./out",
|
|
416
|
+
onlyDirectories: true,
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
### Autocomplete
|
|
423
|
+
|
|
424
|
+
Text input with a suggestion list. Accepts any string, not only suggestions.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
function autocomplete(config: AutocompleteConfig): Promise<string>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
interface AutocompleteConfig {
|
|
432
|
+
message: string;
|
|
433
|
+
choices: string[];
|
|
434
|
+
default?: string | null;
|
|
435
|
+
validate?: ValidateFn<string>;
|
|
436
|
+
filter?: FilterFn<string>;
|
|
437
|
+
transformer?: TransformerFn<string>;
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { autocomplete } from "inquirer-ai";
|
|
443
|
+
|
|
444
|
+
const color = await autocomplete({
|
|
445
|
+
message: "Favorite color",
|
|
446
|
+
choices: ["red", "green", "blue", "yellow"],
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Choices
|
|
451
|
+
|
|
452
|
+
List-based prompts (Select, Checkbox, Search, Rawlist) accept a `RawChoice<V>[]`
|
|
453
|
+
array. A `RawChoice` is a union of `string | Choice<V> | Separator`.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
interface Choice<V = unknown> {
|
|
457
|
+
name: string;
|
|
458
|
+
value: V;
|
|
459
|
+
disabled?: boolean | string;
|
|
460
|
+
short?: string;
|
|
461
|
+
description?: string;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
interface Separator {
|
|
465
|
+
type: "separator";
|
|
466
|
+
text: string;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
type RawChoice<V = unknown> = string | Choice<V> | Separator;
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Use `createSeparator` to create a separator with a default or custom divider:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { createSeparator } from "inquirer-ai";
|
|
476
|
+
|
|
477
|
+
createSeparator(); // "────────"
|
|
478
|
+
createSeparator("── Experimental ──"); // custom text
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Passing a plain string as a choice is shorthand for `{ name: s, value: s }`.
|
|
482
|
+
|
|
483
|
+
Disabled choices appear grayed out in the terminal UI and cannot be selected.
|
|
484
|
+
Set `disabled` to `true` for a generic disable, or to a string (e.g.
|
|
485
|
+
`"coming soon"`) to show a reason.
|
|
486
|
+
|
|
487
|
+
## Validation, Filter, and Transformer
|
|
488
|
+
|
|
489
|
+
All config types extend `BaseConfig<T>`, which accepts three optional callbacks:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
type ValidateFn<T> = (value: T) => boolean | string | null | undefined;
|
|
493
|
+
type FilterFn<T> = (value: T) => T;
|
|
494
|
+
type TransformerFn<T> = (value: T) => string;
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
- **Filter** runs first and transforms the raw answer before validation.
|
|
498
|
+
- **Validate** runs second. Return `true`, `null`, or `undefined` to accept.
|
|
499
|
+
Return a string to reject with that string as the error message. In terminal
|
|
500
|
+
mode the prompt re-asks; in agent mode a validation error is sent back to the
|
|
501
|
+
agent (up to 3 retries).
|
|
502
|
+
- **Transformer** controls how the confirmed answer is displayed in the
|
|
503
|
+
terminal. It does not affect the returned value.
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { text } from "inquirer-ai";
|
|
507
|
+
|
|
508
|
+
const username = await text({
|
|
509
|
+
message: "Username",
|
|
510
|
+
filter: (s) => s.toLowerCase().trim(),
|
|
511
|
+
validate: (s) => (s.length >= 3 ? true : "Must be at least 3 characters"),
|
|
512
|
+
transformer: (s) => s.toUpperCase(),
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Agent Protocol
|
|
517
|
+
|
|
518
|
+
When `isAgentMode()` returns `true` (non-TTY stdin, or
|
|
519
|
+
`INQUIRER_AI_MODE=agent`), every prompt communicates over a JSONL protocol on
|
|
520
|
+
stdout/stdin instead of rendering a terminal UI.
|
|
521
|
+
|
|
522
|
+
1. On the first prompt call the library emits a **handshake** line:
|
|
523
|
+
|
|
524
|
+
```json
|
|
525
|
+
{"kind":"handshake","protocol":"inquirer-ai","version":"0.2.0","format":"jsonl","interaction":"sequential","total":null,"description":"...","example_response":{"answer":"<value>"}}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
2. Each prompt emits a **question** JSON line on stdout:
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
{"kind":"prompt","step":1,"total":null,"type":"select","message":"Template","choices":[{"name":"Web API","value":"web-api"},{"name":"CLI Tool","value":"cli-tool"}]}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
3. The agent replies with a single JSON line on stdin:
|
|
535
|
+
|
|
536
|
+
```json
|
|
537
|
+
{"answer":"web-api"}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
4. The next prompt emits the next question, and so on (sequential,
|
|
541
|
+
one-at-a-time).
|
|
542
|
+
|
|
543
|
+
This is the same JSONL protocol used by the Go and Python `inquirer-ai`
|
|
544
|
+
packages. See [spec/protocol.md](../spec/protocol.md) for the full
|
|
545
|
+
specification.
|
|
546
|
+
|
|
547
|
+
### Mode detection
|
|
548
|
+
|
|
549
|
+
`isAgentMode()` auto-detects based on whether stdin is a TTY. Override with
|
|
550
|
+
the environment variable:
|
|
551
|
+
|
|
552
|
+
```sh
|
|
553
|
+
INQUIRER_AI_MODE=agent node myapp.js # force agent mode
|
|
554
|
+
INQUIRER_AI_MODE=human node myapp.js # force terminal mode
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Custom file descriptors
|
|
558
|
+
|
|
559
|
+
The agent protocol reads/writes on stdin/stdout by default. Override with
|
|
560
|
+
environment variables to use separate file descriptors:
|
|
561
|
+
|
|
562
|
+
```sh
|
|
563
|
+
INQUIRER_AI_FD_OUT=3 INQUIRER_AI_FD_IN=4 node myapp.js
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Error Handling
|
|
567
|
+
|
|
568
|
+
All prompt functions throw on failure. The library defines an error hierarchy
|
|
569
|
+
rooted at `InquirerAIError`:
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
class InquirerAIError extends Error {}
|
|
573
|
+
class ValidationError extends InquirerAIError {}
|
|
574
|
+
class InvalidChoiceError extends ValidationError {}
|
|
575
|
+
class PromptAbortedError extends InquirerAIError {}
|
|
576
|
+
class EditorError extends InquirerAIError {}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
| Error | When |
|
|
580
|
+
|---|---|
|
|
581
|
+
| `PromptAbortedError` | User pressed Ctrl+C or stdin closed |
|
|
582
|
+
| `ValidationError` | Validate callback rejected input / agent retries exhausted |
|
|
583
|
+
| `InvalidChoiceError` | Answer not in the choice list |
|
|
584
|
+
| `EditorError` | `$EDITOR` process failed or not found |
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
import { select, PromptAbortedError } from "inquirer-ai";
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
const result = await select(config);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
if (err instanceof PromptAbortedError) {
|
|
593
|
+
console.log("User cancelled.");
|
|
594
|
+
process.exit(0);
|
|
595
|
+
}
|
|
596
|
+
throw err;
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Theming
|
|
601
|
+
|
|
602
|
+
`setTheme` overrides the default theme. Call it at program start to customize
|
|
603
|
+
colors and symbols.
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
import { setTheme, type Theme } from "inquirer-ai";
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
interface Theme {
|
|
611
|
+
question: string; // hex color for the question prefix (default "#9fa4e3")
|
|
612
|
+
success: string; // hex color for success prefix (default "#62bfa1")
|
|
613
|
+
pointer: string; // hex color for cursor arrow (default "#9c99ec")
|
|
614
|
+
highlight: string; // hex color for focused item text (default "#90bbe9")
|
|
615
|
+
selected: string; // hex color for checked items (default "#59bca4")
|
|
616
|
+
answer: string; // hex color for confirmed answer (default "#9db9dd")
|
|
617
|
+
error: string; // hex color for validation errors (default "#d77780")
|
|
618
|
+
muted: string; // hex color for hints, disabled items (default "#84858f")
|
|
619
|
+
symQuestion: string; // prefix for the question line (default "?")
|
|
620
|
+
symSuccess: string; // prefix after successful answer (default "✓")
|
|
621
|
+
symPointer: string; // cursor indicator in lists (default "❯")
|
|
622
|
+
symChecked: string; // checked checkbox mark (default "◉")
|
|
623
|
+
symUnchecked: string; // unchecked checkbox mark (default "◯")
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
setTheme({
|
|
629
|
+
symQuestion: ">",
|
|
630
|
+
symSuccess: "[ok]",
|
|
631
|
+
symPointer: "->",
|
|
632
|
+
symChecked: "[x]",
|
|
633
|
+
symUnchecked: "[ ]",
|
|
634
|
+
});
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
`setTheme` accepts a `Partial<Theme>`, so you only need to specify the fields
|
|
638
|
+
you want to change. Use `getTheme()` to read the current theme.
|
|
639
|
+
|
|
640
|
+
## License
|
|
641
|
+
|
|
642
|
+
MIT
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAYA,wBAAgB,UAAU,IAAI,IAAI,CAajC;AAkHD,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,wBAAgB,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEhE;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAkCrD"}
|
package/dist/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as readline from "node:readline";
|
|
3
|
-
|
|
3
|
+
import { VERSION } from "./version.js";
|
|
4
4
|
let handshakeSent = false;
|
|
5
5
|
let handshakeAck = null;
|
|
6
6
|
let rl = null;
|
|
@@ -25,14 +25,38 @@ export function resetAgent() {
|
|
|
25
25
|
function getOutputStream() {
|
|
26
26
|
const fdOut = process.env.INQUIRER_AI_FD_OUT;
|
|
27
27
|
if (fdOut) {
|
|
28
|
-
|
|
28
|
+
const fd = parseInt(fdOut, 10);
|
|
29
|
+
if (Number.isNaN(fd)) {
|
|
30
|
+
process.stderr.write(`[inquirer-ai] Warning: invalid INQUIRER_AI_FD_OUT="${fdOut}", falling back to stdout\n`);
|
|
31
|
+
return process.stdout;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return fs.createWriteStream("", { fd });
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
process.stderr.write(`[inquirer-ai] Warning: failed to open fd ${fd} for output: ${msg}, falling back to stdout\n`);
|
|
39
|
+
return process.stdout;
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
return process.stdout;
|
|
31
43
|
}
|
|
32
44
|
function getInputStream() {
|
|
33
45
|
const fdIn = process.env.INQUIRER_AI_FD_IN;
|
|
34
46
|
if (fdIn) {
|
|
35
|
-
|
|
47
|
+
const fd = parseInt(fdIn, 10);
|
|
48
|
+
if (Number.isNaN(fd)) {
|
|
49
|
+
process.stderr.write(`[inquirer-ai] Warning: invalid INQUIRER_AI_FD_IN="${fdIn}", falling back to stdin\n`);
|
|
50
|
+
return process.stdin;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return fs.createReadStream("", { fd });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
57
|
+
process.stderr.write(`[inquirer-ai] Warning: failed to open fd ${fd} for input: ${msg}, falling back to stdin\n`);
|
|
58
|
+
return process.stdin;
|
|
59
|
+
}
|
|
36
60
|
}
|
|
37
61
|
return process.stdin;
|
|
38
62
|
}
|
|
@@ -124,7 +148,11 @@ export async function agentReceive() {
|
|
|
124
148
|
}
|
|
125
149
|
let resp;
|
|
126
150
|
try {
|
|
127
|
-
|
|
151
|
+
const raw = JSON.parse(line);
|
|
152
|
+
if (typeof raw !== "object" || raw === null) {
|
|
153
|
+
throw new SyntaxError("not an object");
|
|
154
|
+
}
|
|
155
|
+
resp = raw;
|
|
128
156
|
}
|
|
129
157
|
catch {
|
|
130
158
|
throw new Error(`Invalid JSON response: ${line.trim()}. Expected JSON like: {"answer": "<value>"}`);
|
|
@@ -133,10 +161,12 @@ export async function agentReceive() {
|
|
|
133
161
|
handshakeAck = resp;
|
|
134
162
|
continue;
|
|
135
163
|
}
|
|
136
|
-
if (
|
|
164
|
+
if (!("answer" in resp)) {
|
|
137
165
|
throw new Error(`Response must be a JSON object with an "answer" key, ` +
|
|
138
166
|
`e.g. {"answer": "<value>"}. Got: ${line.trim()}`);
|
|
139
167
|
}
|
|
168
|
+
// Return the raw answer value; the caller (BasePrompt.executeAgent) passes it
|
|
169
|
+
// through validateAnswer() which narrows it to the prompt's concrete type T.
|
|
140
170
|
return resp.answer;
|
|
141
171
|
}
|
|
142
172
|
}
|