inquirer-ai 0.2.1 → 0.3.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 (45) hide show
  1. package/README.md +642 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +35 -5
  4. package/dist/agent.js.map +1 -1
  5. package/dist/choice.d.ts.map +1 -1
  6. package/dist/choice.js +3 -0
  7. package/dist/choice.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/prompts/base.d.ts.map +1 -1
  13. package/dist/prompts/base.js +19 -4
  14. package/dist/prompts/base.js.map +1 -1
  15. package/dist/prompts/checkbox.d.ts +2 -0
  16. package/dist/prompts/checkbox.d.ts.map +1 -1
  17. package/dist/prompts/checkbox.js +13 -2
  18. package/dist/prompts/checkbox.js.map +1 -1
  19. package/dist/prompts/expand.d.ts.map +1 -1
  20. package/dist/prompts/expand.js +9 -5
  21. package/dist/prompts/expand.js.map +1 -1
  22. package/dist/prompts/number.d.ts +5 -0
  23. package/dist/prompts/number.d.ts.map +1 -1
  24. package/dist/prompts/number.js +42 -5
  25. package/dist/prompts/number.js.map +1 -1
  26. package/dist/prompts/search.d.ts +4 -2
  27. package/dist/prompts/search.d.ts.map +1 -1
  28. package/dist/prompts/search.js +110 -15
  29. package/dist/prompts/search.js.map +1 -1
  30. package/dist/prompts/select.d.ts.map +1 -1
  31. package/dist/prompts/select.js +11 -1
  32. package/dist/prompts/select.js.map +1 -1
  33. package/dist/prompts/text.d.ts +4 -0
  34. package/dist/prompts/text.d.ts.map +1 -1
  35. package/dist/prompts/text.js +13 -4
  36. package/dist/prompts/text.js.map +1 -1
  37. package/dist/socket.d.ts +21 -0
  38. package/dist/socket.d.ts.map +1 -0
  39. package/dist/socket.js +334 -0
  40. package/dist/socket.js.map +1 -0
  41. package/dist/version.d.ts +2 -0
  42. package/dist/version.d.ts.map +1 -0
  43. package/dist/version.js +5 -0
  44. package/dist/version.js.map +1 -0
  45. 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
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAaA,wBAAgB,UAAU,IAAI,IAAI,CAajC;AAoFD,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,CA4BrD"}
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
- const VERSION = "0.2.0";
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
- return fs.createWriteStream("", { fd: parseInt(fdOut, 10) });
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
- return fs.createReadStream("", { fd: parseInt(fdIn, 10) });
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
- resp = JSON.parse(line);
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 (typeof resp !== "object" || resp === null || !("answer" in resp)) {
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
  }